Преглед на файлове

feat: add block settings

Ahmad Kholid преди 3 години
родител
ревизия
5b5e734009

BIN
src/assets/images/default.png


BIN
src/assets/images/smooth-step.png


BIN
src/assets/images/step.png


BIN
src/assets/images/straight.png


+ 1 - 1
src/background/workflowEngine/blocksHandler/handlerExecuteWorkflow.js

@@ -85,7 +85,7 @@ async function executeWorkflow({ id: blockId, data }) {
     blocksHandler: this.engine.blocksHandler,
   };
 
-  const isWorkflowIncluded = workflow.nodes.some(
+  const isWorkflowIncluded = workflow.drawflow.nodes.some(
     (node) =>
       node.label === 'execute-workflow' &&
       node.data.workflowId === this.engine.workflow.id

+ 2 - 2
src/components/newtab/workflow/WorkflowEditBlock.vue

@@ -43,7 +43,7 @@
         connections: data.id === 'wait-connections' ? data.connections : null,
       }"
     />
-    <on-block-error
+    <edit-block-settings
       v-if="!excludeOnError.includes(data.id)"
       :key="data.itemId || data.blockId"
       :data="data"
@@ -56,7 +56,7 @@
 import { computed } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { excludeOnError } from '@/utils/shared';
-import OnBlockError from './edit/OnBlockError.vue';
+import EditBlockSettings from './edit/EditBlockSettings.vue';
 
 const editComponents = require.context(
   './edit',

+ 10 - 23
src/components/newtab/workflow/WorkflowEditor.vue

@@ -1,5 +1,12 @@
 <template>
-  <vue-flow :id="props.id" :class="{ disabled: options.disabled }">
+  <vue-flow
+    :id="props.id"
+    :class="{ disabled: options.disabled }"
+    :default-edge-options="{
+      type: settings.lineType,
+      markerEnd: settings.arrow ? MarkerType.ArrowClosed : '',
+    }"
+  >
     <Background />
     <MiniMap v-if="minimap" :node-class-name="minimapNodeClassName" />
     <div
@@ -126,28 +133,11 @@ const editor = useVueFlow({
 });
 editor.onConnect((params) => {
   params.class = `source-${params.sourceHandle} target-${params.targetHandle}`;
-  /* eslint-disable-next-line */
-  params = applyEdgeSettings(params);
-
   editor.addEdges([params]);
 });
 
-function applyEdgeSettings(edge) {
-  const settings = store.settings.editor;
-  if (settings.lineType !== 'default') {
-    edge.type = settings.lineType;
-  } else {
-    delete edge.type;
-  }
-
-  if (settings.arrow) {
-    edge.markerEnd = MarkerType.ArrowClosed;
-  } else {
-    delete edge.markerEnd;
-  }
+const settings = store.settings.editor;
 
-  return edge;
-}
 function minimapNodeClassName({ label }) {
   const { category } = tasks[label];
   const { color } = categories[category];
@@ -185,16 +175,13 @@ function onMousedown(event) {
   }
 }
 function applyFlowData() {
-  const settings = store.settings.editor;
-  const edges = (props.data.edges || []).map((edge) => applyEdgeSettings(edge));
-
   if (settings.snapToGrid) {
     editor.snapToGrid.value = true;
     editor.snapGrid.value = Object.values(settings.snapGrid);
   }
 
   editor.setNodes(props.data.nodes || []);
-  editor.setEdges(edges);
+  editor.setEdges(props.data.edges || []);
   editor.setTransform({
     x: props.data.x || 0,
     y: props.data.y || 0,

+ 89 - 0
src/components/newtab/workflow/edit/BlockSetting/BlockSettingLines.vue

@@ -0,0 +1,89 @@
+<template>
+  <div class="block-lines max-w-xl">
+    <ui-select
+      v-model="state.activeEdge"
+      :placeholder="t('workflow.blocks.base.settings.line.select')"
+      class="w-full"
+    >
+      <option v-for="edge in state.edges" :key="edge.id" :value="edge.id">
+        {{ edge.name }}
+      </option>
+    </ui-select>
+    <div v-if="activeEdge" class="mt-4">
+      <div class="flex items-center mt-2">
+        <label class="flex items-center mr-4 mt-5">
+          <ui-switch
+            :model-value="activeEdge.animated"
+            @change="updateActiveEdge('animated', $event)"
+          />
+          <span class="ml-2">
+            {{ t('workflow.blocks.base.settings.line.animated') }}
+          </span>
+        </label>
+        <ui-input
+          :model-value="activeEdge.label"
+          :label="t('workflow.blocks.base.settings.line.label')"
+          placeholder="A label"
+          class="flex-1"
+          @change="updateActiveEdge('label', $event)"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+<script setup>
+import { inject, onMounted, reactive, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { debounce } from '@/utils/helper';
+
+const props = defineProps({
+  blockId: {
+    type: String,
+    default: '',
+  },
+});
+
+const { t } = useI18n();
+
+const editor = inject('workflow-editor');
+const state = reactive({
+  retrieved: false,
+  edges: {},
+  activeEdge: '',
+});
+
+const activeEdge = computed(() => state.edges[state.activeEdge]);
+
+const updateActiveEdge = debounce((name, value) => {
+  const edge = editor.value.getEdge.value(state.activeEdge);
+  console.log(name, value);
+  edge[name] = value;
+  state.edges[state.activeEdge][name] = value;
+}, 250);
+
+onMounted(() => {
+  state.edges = editor.value.getEdges.value.reduce(
+    (acc, { id, source, targetNode, label, animated, labelStyle }) => {
+      if (source !== props.blockId) return acc;
+
+      let name = t('workflow.blocks.base.settings.line.to', {
+        name: t(`workflow.blocks.${targetNode.label}.name`),
+      });
+      if (targetNode.data.description) {
+        name += ` (${targetNode.data.description.slice(0, 32)})`;
+      }
+
+      acc[id] = {
+        name,
+        id,
+        label: `${label || ''}`,
+        animated: animated ?? false,
+        labelStyle: labelStyle || '',
+      };
+
+      return acc;
+    },
+    {}
+  );
+});
+</script>

+ 116 - 0
src/components/newtab/workflow/edit/BlockSetting/BlockSettingOnError.vue

@@ -0,0 +1,116 @@
+<template>
+  <div class="on-block-error">
+    <div
+      class="p-4 rounded-lg bg-green-200 dark:bg-green-300 flex items-start text-black"
+    >
+      <v-remixicon name="riInformationLine" />
+      <p class="flex-1 ml-4">
+        {{ t('workflow.blocks.base.onError.info') }}
+      </p>
+    </div>
+    <div class="mt-4">
+      <label class="inline-flex">
+        <ui-switch v-model="state.enable" />
+        <span class="ml-2">
+          {{ t('common.enable') }}
+        </span>
+      </label>
+      <template v-if="state.enable">
+        <div class="mt-2">
+          <label class="inline-flex">
+            <ui-switch v-model="state.retry" />
+            <span class="ml-2">
+              {{ t('workflow.blocks.base.onError.retry') }}
+            </span>
+          </label>
+        </div>
+        <transition-expand>
+          <div v-if="state.retry" class="mt-2">
+            <div class="inline-flex items-center">
+              <span>
+                {{ t('workflow.blocks.base.onError.times.name') }}
+              </span>
+              <v-remixicon
+                :title="t('workflow.blocks.base.onError.times.description')"
+                name="riInformationLine"
+                size="20"
+                class="mr-2"
+              />
+              <ui-input
+                v-model.number="state.retryTimes"
+                type="number"
+                min="0"
+                class="w-20"
+              />
+            </div>
+            <div class="inline-flex items-center ml-12">
+              <span>
+                {{ t('workflow.blocks.base.onError.interval.name') }}
+              </span>
+              <v-remixicon
+                :title="t('workflow.blocks.base.onError.interval.description')"
+                name="riInformationLine"
+                size="20"
+                class="mr-2"
+              />
+              <ui-input
+                v-model.number="state.retryInterval"
+                type="number"
+                min="0"
+                class="w-20"
+              />
+              <span class="ml-1">
+                {{ t('workflow.blocks.base.onError.interval.second') }}
+              </span>
+            </div>
+          </div>
+        </transition-expand>
+        <ui-select v-model="state.toDo" class="mt-2 w-56">
+          <option
+            v-for="type in toDoTypes"
+            :key="type"
+            :value="type"
+            :disabled="type === 'fallback' && data.isInGroup ? true : null"
+            class="to-do-type"
+          >
+            {{ t(`workflow.blocks.base.onError.toDo.${type}`) }}
+          </option>
+        </ui-select>
+      </template>
+    </div>
+  </div>
+</template>
+<script setup>
+import { reactive, watch, onMounted } from 'vue';
+import { useI18n } from 'vue-i18n';
+
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+const emit = defineEmits(['change']);
+
+const toDoTypes = ['error', 'continue', 'fallback'];
+
+const { t } = useI18n();
+const state = reactive({});
+
+watch(
+  () => state,
+  (onError) => {
+    emit('change', onError);
+  },
+  { deep: true }
+);
+
+onMounted(() => {
+  Object.assign(state, props.data);
+});
+</script>
+<style scoped>
+.to-do-type.is-active {
+  @apply bg-accent dark:text-black text-gray-100 !important;
+}
+</style>

+ 103 - 0
src/components/newtab/workflow/edit/EditBlockSettings.vue

@@ -0,0 +1,103 @@
+<template>
+  <div class="block-settings">
+    <ui-button
+      :class="{ 'text-primary': state.onError.enable }"
+      @click="state.showModal = true"
+    >
+      <v-remixicon name="riShieldLine" class="-ml-1 mr-2" />
+      <span>
+        {{ t('workflow.blocks.base.settings.title') }}
+      </span>
+    </ui-button>
+    <ui-modal
+      v-model="state.showModal"
+      :title="t('workflow.blocks.base.settings.title')"
+      content-class="max-w-xl block-settings"
+    >
+      <ui-tabs v-model="state.activeTab" class="-mt-2">
+        <ui-tab v-for="tab in tabs" :key="tab.id" :value="tab.id">
+          {{ tab.name }}
+        </ui-tab>
+      </ui-tabs>
+      <ui-tab-panels
+        v-if="state.retrieved"
+        v-model="state.activeTab"
+        class="mt-4"
+      >
+        <ui-tab-panel value="on-error">
+          <block-setting-on-error
+            v-model:data="state.onError"
+            @change="onErrorChange"
+          />
+        </ui-tab-panel>
+        <ui-tab-panel value="lines">
+          <block-setting-lines :block-id="data.blockId" />
+        </ui-tab-panel>
+      </ui-tab-panels>
+    </ui-modal>
+  </div>
+</template>
+<script setup>
+import { reactive, onMounted } from 'vue';
+import { useI18n } from 'vue-i18n';
+import defu from 'defu';
+import BlockSettingLines from './BlockSetting/BlockSettingLines.vue';
+import BlockSettingOnError from './BlockSetting/BlockSettingOnError.vue';
+
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+  editor: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+const emit = defineEmits(['change']);
+
+const { t } = useI18n();
+
+const tabs = [
+  { id: 'on-error', name: t('workflow.blocks.base.onError.button') },
+  { id: 'lines', name: t('workflow.blocks.base.settings.line.title') },
+];
+const defaultSettings = {
+  onError: {
+    retry: false,
+    enable: false,
+    retryTimes: 1,
+    retryInterval: 2,
+    toDo: 'error',
+  },
+};
+
+const state = reactive({
+  showModal: false,
+  retrieved: false,
+  activeTab: 'on-error',
+  onError: defaultSettings.onError,
+});
+
+function onErrorChange(data) {
+  if (!state.retrieved) return;
+
+  state.onError = data;
+  emit('change', data);
+}
+
+onMounted(() => {
+  const onErrorSetting = defu(
+    props.data.data.onError || {},
+    defaultSettings.onError
+  );
+
+  state.onError = onErrorSetting;
+  state.retrieved = true;
+});
+</script>
+<style>
+.block-settings {
+  min-height: 500px;
+}
+</style>

+ 0 - 145
src/components/newtab/workflow/edit/OnBlockError.vue

@@ -1,145 +0,0 @@
-<template>
-  <div class="on-block-error">
-    <ui-button
-      :class="{ 'text-primary': state.data.enable }"
-      @click="state.showModal = true"
-    >
-      <v-remixicon name="riShieldLine" class="-ml-1 mr-2" />
-      <span>
-        {{ t('workflow.blocks.base.onError.button') }}
-      </span>
-    </ui-button>
-    <ui-modal
-      v-model="state.showModal"
-      :title="t('workflow.blocks.base.onError.title')"
-      content-class="max-w-xl"
-    >
-      <div
-        class="p-4 rounded-lg bg-green-200 dark:bg-green-300 flex items-start text-black"
-      >
-        <v-remixicon name="riInformationLine" />
-        <p class="flex-1 ml-4">
-          {{ t('workflow.blocks.base.onError.info') }}
-        </p>
-      </div>
-      <div class="mt-8">
-        <label class="inline-flex">
-          <ui-switch v-model="state.data.enable" />
-          <span class="ml-2">
-            {{ t('common.enable') }}
-          </span>
-        </label>
-        <template v-if="state.data.enable">
-          <div class="mt-4">
-            <label class="inline-flex">
-              <ui-switch v-model="state.data.retry" />
-              <span class="ml-2">
-                {{ t('workflow.blocks.base.onError.retry') }}
-              </span>
-            </label>
-          </div>
-          <transition-expand>
-            <div v-if="state.data.retry" class="mt-2">
-              <div class="inline-flex items-center">
-                <span>
-                  {{ t('workflow.blocks.base.onError.times.name') }}
-                </span>
-                <v-remixicon
-                  :title="t('workflow.blocks.base.onError.times.description')"
-                  name="riInformationLine"
-                  size="20"
-                  class="mr-2"
-                />
-                <ui-input
-                  v-model.number="state.data.retryTimes"
-                  type="number"
-                  min="0"
-                  class="w-20"
-                />
-              </div>
-              <div class="inline-flex items-center ml-12">
-                <span>
-                  {{ t('workflow.blocks.base.onError.interval.name') }}
-                </span>
-                <v-remixicon
-                  :title="
-                    t('workflow.blocks.base.onError.interval.description')
-                  "
-                  name="riInformationLine"
-                  size="20"
-                  class="mr-2"
-                />
-                <ui-input
-                  v-model.number="state.data.retryInterval"
-                  type="number"
-                  min="0"
-                  class="w-20"
-                />
-                <span class="ml-1">
-                  {{ t('workflow.blocks.base.onError.interval.second') }}
-                </span>
-              </div>
-            </div>
-          </transition-expand>
-          <ui-select v-model="state.data.toDo" class="mt-4 w-56">
-            <option
-              v-for="type in toDoTypes"
-              :key="type"
-              :value="type"
-              :disabled="type === 'fallback' && data.isInGroup ? true : null"
-              class="to-do-type"
-            >
-              {{ t(`workflow.blocks.base.onError.toDo.${type}`) }}
-            </option>
-          </ui-select>
-        </template>
-      </div>
-    </ui-modal>
-  </div>
-</template>
-<script setup>
-import { reactive, watch, onMounted } from 'vue';
-import { useI18n } from 'vue-i18n';
-
-const props = defineProps({
-  data: {
-    type: Object,
-    default: () => ({}),
-  },
-});
-const emit = defineEmits(['change']);
-
-const { t } = useI18n();
-
-const toDoTypes = ['error', 'continue', 'fallback'];
-
-const state = reactive({
-  showModal: false,
-  data: {
-    retry: false,
-    enable: false,
-    retryTimes: 1,
-    retryInterval: 2,
-    toDo: 'error',
-  },
-});
-
-watch(
-  () => state.data,
-  (onError) => {
-    if (!state.showModal) return;
-
-    emit('change', onError);
-  },
-  { deep: true }
-);
-
-onMounted(() => {
-  state.data = Object.assign(state.data, props.data.data.onError || {});
-});
-</script>
-<style scoped>
-.to-do-type.is-active {
-  @apply bg-accent dark:text-black text-gray-100 !important;
-}
-</style>

+ 6 - 0
src/components/ui/UiInput.vue

@@ -133,3 +133,9 @@ export default {
   },
 };
 </script>
+<style>
+.input-ui input[type='color'] {
+  padding-top: 0 !important;
+  padding-bottom: 0 !important;
+}
+</style>

+ 0 - 116
src/lib/drawflow.js

@@ -1,116 +0,0 @@
-import { h, render } from 'vue';
-import Drawflow from 'drawflow';
-import '@/assets/css/drawflow.css';
-
-const blockComponents = require.context('../components/block', false, /\.vue$/);
-
-export default function (element, { context, options = {} }) {
-  const editor = new Drawflow(element, { render, version: 3, h }, context);
-
-  editor.useuuid = true;
-  editor.translate_to = function (x, y, zoom) {
-    if (typeof x !== 'number' || typeof y !== 'number') return;
-
-    this.canvas_x = x;
-    this.canvas_y = y;
-
-    this.zoom = 1;
-
-    this.precanvas.style.transform = `translate(${this.canvas_x}px, ${this.canvas_y}px) scale(${this.zoom})`;
-    this.zoom = zoom;
-    this.zoom_last_value = 1;
-    this.zoom_refresh();
-  };
-  editor.createCurvature = (
-    startPosX,
-    startPosY,
-    endPosX,
-    endPosY,
-    curvatureValue,
-    type
-  ) => {
-    const curvature = options.disableCurvature ? 0 : curvatureValue;
-    const generateCurvature = (start = false) => {
-      if (start) {
-        return startPosX + Math.abs(endPosX - startPosX) * curvature;
-      }
-
-      return endPosX - Math.abs(endPosX - startPosX) * curvature;
-    };
-
-    switch (type) {
-      case 'open': {
-        const hx1 = generateCurvature(true);
-        let hx2 = generateCurvature();
-
-        if (startPosX >= endPosX) {
-          hx2 = endPosX - Math.abs(endPosX - startPosX) * (curvature * -1);
-        }
-
-        return ` M ${startPosX} ${startPosY} C ${hx1} ${startPosY} ${hx2} ${endPosY} ${endPosX}  ${endPosY}`;
-      }
-      case 'close': {
-        let hx1 = generateCurvature(true);
-        const hx2 = generateCurvature();
-
-        if (startPosX >= endPosX) {
-          hx1 = startPosX + Math.abs(endPosX - startPosX) * (curvature * -1);
-        }
-
-        const posX = options.arrow ? endPosX - 10 : endPosX;
-
-        return ` M ${startPosX} ${startPosY} C ${hx1} ${startPosY} ${hx2} ${endPosY} ${posX} ${endPosY}`;
-      }
-      case 'other': {
-        let hx1 = generateCurvature(true);
-        let hx2 = generateCurvature();
-
-        if (startPosX >= endPosX) {
-          hx1 = startPosX + Math.abs(endPosX - startPosX) * (curvature * -1);
-          hx2 = endPosX - Math.abs(endPosX - startPosX) * (curvature * -1);
-        }
-
-        return ` M ${startPosX} ${startPosY} C ${hx1} ${startPosY} ${hx2} ${endPosY} ${endPosX} ${endPosY}`;
-      }
-      default: {
-        let line = '';
-        const posX = options.arrow ? endPosX - 10 : endPosX;
-
-        if (!options.disableCurvature) {
-          const hx1 = generateCurvature(true);
-          const hx2 = generateCurvature();
-
-          line = `M${startPosX} ${startPosY} C${hx1} ${startPosY} ${hx2} ${endPosY} ${posX} ${endPosY}`;
-        } else {
-          const centerX =
-            Math.abs(endPosX - startPosX) < 300
-              ? (endPosX - startPosX) / 2 + startPosX
-              : startPosX + 150;
-          let firstLine = `L${centerX} ${startPosY} L${centerX} ${endPosY}`;
-
-          if (startPosX >= endPosX) {
-            const centerY = (endPosY - startPosY) / 2 + startPosY;
-
-            firstLine = ` L${startPosX} ${startPosY} L${startPosX} ${centerY} L${posX} ${centerY}`;
-          }
-
-          line = `M ${startPosX} ${startPosY} ${firstLine} L${posX} ${endPosY}`;
-        }
-
-        return line;
-      }
-    }
-  };
-
-  Object.entries(options).forEach(([key, value]) => {
-    editor[key] = value;
-  });
-
-  blockComponents.keys().forEach((key) => {
-    const name = key.replace(/(.\/)|\.vue$/g, '');
-
-    editor.registerNode(name, blockComponents(key).default, { editor }, {});
-  });
-
-  return editor;
-}

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

@@ -17,6 +17,16 @@
         "timeout": "Timeout (milliseconds)",
         "noPermission": "Automa don't have enough permission to do this action",
         "grantPermission": "Grant permission",
+        "settings": {
+          "title": "Block settings",
+          "line": {
+            "title": "Lines",
+            "label": "Line label",
+            "animated": "Animated",
+            "select": "Select line",
+            "to": "Line to {name} block",
+          }
+        },
         "toggle": {
           "enable": "Enable block",
           "disable": "Disable block",

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

@@ -255,6 +255,7 @@ provide('workflow', {
   editState,
   data: workflow,
 });
+provide('workflow-editor', editor);
 
 const updateBlockData = debounce((data) => {
   const node = editor.value.getNode.value(editState.blockData.blockId);