فهرست منبع

feat: add option to disable workflow

Ahmad Kholid 3 سال پیش
والد
کامیت
f6edc8d0df

+ 10 - 0
src/background/collection-engine/flow-handler.js

@@ -13,6 +13,16 @@ export function workflow(flow) {
       return;
     }
 
+    if (currentWorkflow.isDisabled) {
+      resolve({
+        type: 'stopped',
+        name: currentWorkflow.name,
+        message: 'workflow-disabled',
+      });
+
+      return;
+    }
+
     const { globalData } = this.collection;
     this.currentWorkflow = currentWorkflow;
 

+ 1 - 0
src/background/collection-engine/index.js

@@ -166,6 +166,7 @@ class CollectionEngine {
             type: data.type || 'success',
             name: data.name,
             logId: data.id,
+            message: data.message,
             duration: Math.round(Date.now() - started),
           });
 

+ 3 - 2
src/background/workflow-engine/index.js

@@ -95,6 +95,8 @@ class WorkflowEngine {
   }
 
   init() {
+    if (this.workflow.isDisabled) return;
+
     const drawflowData =
       typeof this.workflow.drawflow === 'string'
         ? JSON.parse(this.workflow.drawflow || '{}')
@@ -330,8 +332,7 @@ class WorkflowEngine {
   _sendMessageToTab(block, options = {}) {
     return new Promise((resolve, reject) => {
       if (!this.tabId) {
-        /* eslint-disable-next-line */
-        reject('no-tab');
+        reject(new Error('no-tab'));
         return;
       }
 

+ 33 - 31
src/components/newtab/shared/SharedCard.vue

@@ -1,36 +1,38 @@
 <template>
   <ui-card class="hover:ring-2 group hover:ring-accent">
-    <div class="flex items-center mb-4">
-      <span class="p-2 rounded-lg bg-box-transparent">
-        <v-remixicon :name="data.icon || icon" />
-      </span>
-      <div class="flex-grow"></div>
-      <button
-        class="invisible group-hover:visible"
-        @click="$emit('execute', data)"
-      >
-        <v-remixicon name="riPlayLine" />
-      </button>
-      <ui-popover v-if="showDetails" class="h-6 ml-2">
-        <template #trigger>
-          <button>
-            <v-remixicon name="riMoreLine" />
-          </button>
-        </template>
-        <ui-list class="w-36 space-y-1">
-          <ui-list-item
-            v-for="item in menu"
-            :key="item.id"
-            v-close-popover
-            class="cursor-pointer"
-            @click="$emit('menuSelected', { id: item.id, data })"
-          >
-            <v-remixicon :name="item.icon" class="mr-2 -ml-1" />
-            <span class="capitalize">{{ item.name }}</span>
-          </ui-list-item>
-        </ui-list>
-      </ui-popover>
-    </div>
+    <slot name="header">
+      <div class="flex items-center mb-4">
+        <span class="p-2 rounded-lg bg-box-transparent">
+          <v-remixicon :name="data.icon || icon" />
+        </span>
+        <div class="flex-grow"></div>
+        <button
+          class="invisible group-hover:visible"
+          @click="$emit('execute', data)"
+        >
+          <v-remixicon name="riPlayLine" />
+        </button>
+        <ui-popover v-if="showDetails" class="h-6 ml-2">
+          <template #trigger>
+            <button>
+              <v-remixicon name="riMoreLine" />
+            </button>
+          </template>
+          <ui-list class="space-y-1" style="min-width: 150px">
+            <ui-list-item
+              v-for="item in menu"
+              :key="item.id"
+              v-close-popover
+              class="cursor-pointer"
+              @click="$emit('menuSelected', { id: item.id, data })"
+            >
+              <v-remixicon :name="item.icon" class="mr-2 -ml-1" />
+              <span class="capitalize">{{ item.name }}</span>
+            </ui-list-item>
+          </ui-list>
+        </ui-popover>
+      </div>
+    </slot>
     <div class="cursor-pointer" @click="$emit('click', data)">
       <p class="line-clamp font-semibold leading-tight">
         {{ data.name }}

+ 6 - 2
src/components/newtab/shared/SharedWorkflowState.vue

@@ -34,7 +34,7 @@
         class="flex items-center py-2"
       >
         <v-remixicon :name="block.icon" />
-        <p class="flex-1 ml-2 mr-4">{{ block.name }}</p>
+        <p class="flex-1 ml-2 mr-4 text-overflow">{{ block.name }}</p>
         <ui-spinner color="text-accnet" size="20" />
       </div>
     </div>
@@ -61,7 +61,11 @@ function getBlock() {
 
   if (Array.isArray(props.data.state.currentBlock)) {
     return props.data.state.currentBlock.map((item) => {
-      if (tasks[item.name]) return t(`workflow.blocks.${item.name}.name`);
+      if (tasks[item.name])
+        return {
+          ...tasks[item.name],
+          name: t(`workflow.blocks.${item.name}.name`),
+        };
 
       return item;
     });

+ 29 - 1
src/components/newtab/workflow/WorkflowActions.vue

@@ -12,6 +12,7 @@
   </ui-card>
   <ui-card padding="p-1 ml-4">
     <button
+      v-if="!workflow.isDisabled"
       v-tooltip.group="t('common.execute')"
       icon
       class="hoverable p-2 rounded-lg"
@@ -19,6 +20,14 @@
     >
       <v-remixicon name="riPlayLine" />
     </button>
+    <button
+      v-else
+      v-tooltip="t('workflow.clickToEnable')"
+      class="p-2"
+      @click="$emit('update', { isDisabled: false })"
+    >
+      {{ t('common.disabled') }}
+    </button>
   </ui-card>
   <ui-card padding="p-1 ml-4 space-x-1">
     <ui-popover>
@@ -28,6 +37,13 @@
         </button>
       </template>
       <ui-list class="w-36">
+        <ui-list-item
+          class="cursor-pointer"
+          @click="$emit('update', { isDisabled: !workflow.isDisabled })"
+        >
+          <v-remixicon name="riToggleLine" class="mr-2 -ml-1" />
+          {{ t(`common.${workflow.isDisabled ? 'enable' : 'disable'}`) }}
+        </ui-list-item>
         <ui-list-item
           v-for="item in moreActions"
           :key="item.id"
@@ -75,8 +91,20 @@ defineProps({
     type: Boolean,
     default: false,
   },
+  workflow: {
+    type: Object,
+    default: () => ({}),
+  },
 });
-defineEmits(['showModal', 'execute', 'rename', 'delete', 'save', 'export']);
+defineEmits([
+  'showModal',
+  'execute',
+  'rename',
+  'delete',
+  'save',
+  'export',
+  'update',
+]);
 
 useGroupTooltip();
 const { t } = useI18n();

+ 15 - 2
src/components/popup/home/HomeWorkflowCard.vue

@@ -11,7 +11,8 @@
         {{ dayjs(workflow.createdAt).fromNow() }}
       </p>
     </div>
-    <button title="Execute" @click="$emit('execute', workflow)">
+    <p v-if="workflow.isDisabled">Disabled</p>
+    <button v-else title="Execute" @click="$emit('execute', workflow)">
       <v-remixicon name="riPlayLine" />
     </button>
     <ui-popover class="h-6">
@@ -21,6 +22,15 @@
         </button>
       </template>
       <ui-list class="w-40 space-y-1">
+        <ui-list-item
+          class="capitalize cursor-pointer"
+          @click="$emit('update', { isDisabled: !workflow.isDisabled })"
+        >
+          <v-remixicon name="riToggleLine" class="mr-2 -ml-1" />
+          <span>{{
+            t(`common.${workflow.isDisabled ? 'enable' : 'disable'}`)
+          }}</span>
+        </ui-list-item>
         <ui-list-item
           v-for="item in menu"
           :key="item.name"
@@ -36,6 +46,7 @@
   </ui-card>
 </template>
 <script setup>
+import { useI18n } from 'vue-i18n';
 import dayjs from '@/lib/dayjs';
 
 defineProps({
@@ -44,7 +55,9 @@ defineProps({
     default: () => ({}),
   },
 });
-defineEmits(['execute', 'rename', 'details', 'delete']);
+defineEmits(['execute', 'rename', 'details', 'delete', 'update']);
+
+const { t } = useI18n();
 
 const menu = [
   { name: 'rename', icon: 'riPencilLine' },

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

@@ -2,6 +2,7 @@ import vRemixicon from 'v-remixicon';
 import {
   riHome5Line,
   riFileCopyLine,
+  riToggleLine,
   riFolderLine,
   riInformationLine,
   riWindow2Line,
@@ -74,6 +75,7 @@ import {
 export const icons = {
   riHome5Line,
   riFileCopyLine,
+  riToggleLine,
   riFolderLine,
   riInformationLine,
   riWindow2Line,

+ 3 - 0
src/locales/en/common.json

@@ -27,6 +27,9 @@
     "globalData": "Global data",
     "fileName": "File name",
     "description": "Description",
+    "disable": "Disable",
+    "disabled": "Disabled",
+    "enable": "Enable",
   },
   "message": {
     "noBlock": "No block",

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

@@ -16,6 +16,7 @@
     "name": "Workflow name",
     "rename": "Rename workflow",
     "add": "Add workflow",
+    "clickToEnable": "Click to enable",
     "dataColumns": {
       "title": "Data columns",
       "placeholder": "Search or add column",
@@ -76,6 +77,7 @@
       "finish": "Finish"
     },
     "messages": {
+      "workflow-disabled": "Workflow is disabled",
       "stop-timeout": "Workflow is stopped because of timeout",
       "no-iframe-id": "Can't find Frame ID for the frame element with \"{selector}\" selector",
       "no-tab": "Can't connect to a tab, use \"New tab\" or \"Active tab\" block before using the \"{name}\" block."

+ 1 - 0
src/models/workflow.js

@@ -21,6 +21,7 @@ class Workflow extends Model {
       globalData: this.string('[{ "key": "value" }]'),
       lastRunAt: this.number(),
       createdAt: this.number(),
+      isDisabled: this.boolean(false),
       settings: this.attr({
         timeout: 120000,
         onError: 'stop-workflow',

+ 2 - 2
src/newtab/pages/Collections.vue

@@ -55,8 +55,8 @@ const dialog = useDialog();
 const { t } = useI18n();
 
 const collectionCardMenu = [
-  { id: 'rename', name: t('collection.rename'), icon: 'riPencilLine' },
-  { id: 'delete', name: t('collection.delete'), icon: 'riDeleteBin7Line' },
+  { id: 'rename', name: t('common.rename'), icon: 'riPencilLine' },
+  { id: 'delete', name: t('common.delete'), icon: 'riDeleteBin7Line' },
 ];
 
 const query = ref('');

+ 60 - 4
src/newtab/pages/Workflows.vue

@@ -48,12 +48,62 @@
     <div v-else class="grid gap-4 grid-cols-5 2xl:grid-cols-6">
       <shared-card
         v-for="workflow in workflows"
-        v-bind="{ data: workflow, menu }"
         :key="workflow.id"
+        :data="workflow"
         @click="$router.push(`/workflows/${$event.id}`)"
-        @execute="executeWorkflow"
-        @menuSelected="menuHandlers[$event.id]($event.data)"
-      />
+      >
+        <template #header>
+          <div class="flex items-center mb-4">
+            <span
+              v-if="!workflow.isDisabled"
+              class="p-2 rounded-lg bg-box-transparent"
+            >
+              <v-remixicon :name="workflow.icon || icon" />
+            </span>
+            <p v-else class="py-2">{{ t('common.disabled') }}</p>
+            <div class="flex-grow"></div>
+            <button
+              v-if="!workflow.isDisabled"
+              class="invisible group-hover:visible"
+              @click="executeWorkflow(workflow)"
+            >
+              <v-remixicon name="riPlayLine" />
+            </button>
+            <ui-popover class="h-6 ml-2">
+              <template #trigger>
+                <button>
+                  <v-remixicon name="riMoreLine" />
+                </button>
+              </template>
+              <ui-list class="space-y-1" style="min-width: 150px">
+                <ui-list-item
+                  class="cursor-pointer"
+                  @click="
+                    updateWorkflow(workflow.id, {
+                      isDisabled: !workflow.isDisabled,
+                    })
+                  "
+                >
+                  <v-remixicon name="riToggleLine" class="mr-2 -ml-1" />
+                  <span class="capitalize">{{
+                    t(`common.${workflow.isDisabled ? 'enable' : 'disable'}`)
+                  }}</span>
+                </ui-list-item>
+                <ui-list-item
+                  v-for="item in menu"
+                  :key="item.id"
+                  v-close-popover
+                  class="cursor-pointer"
+                  @click="menuHandlers[item.id](workflow)"
+                >
+                  <v-remixicon :name="item.icon" class="mr-2 -ml-1" />
+                  <span class="capitalize">{{ item.name }}</span>
+                </ui-list-item>
+              </ui-list>
+            </ui-popover>
+          </div>
+        </template>
+      </shared-card>
     </div>
   </div>
 </template>
@@ -95,6 +145,12 @@ const workflows = computed(() =>
 function executeWorkflow(workflow) {
   sendMessage('workflow:execute', workflow, 'background');
 }
+function updateWorkflow(id, data) {
+  Workflow.update({
+    where: id,
+    data,
+  });
+}
 function newWorkflow() {
   dialog.prompt({
     title: t('workflow.new'),

+ 10 - 2
src/newtab/pages/logs/[id].vue

@@ -26,6 +26,7 @@
           <router-link
             v-if="collectionLog"
             :to="activeLog.collectionLogId"
+            replace
             class="mb-4 flex"
           >
             <v-remixicon name="riArrowLeftLine" class="mr-2" />
@@ -148,7 +149,11 @@ const pagination = shallowReactive({
 
 function translateLog(log) {
   const { name, message, type } = log;
-  const getTranslatation = (path, def) => (te(path) ? t(path) : def);
+  const getTranslatation = (path, def) => {
+    const params = typeof path === 'string' ? { path } : path;
+
+    return te(params.path) ? t(params.path, params.params) : def;
+  };
 
   if (['finish', 'stop'].includes(type)) {
     log.name = t(`log.types.${type}`);
@@ -156,7 +161,10 @@ function translateLog(log) {
     log.name = getTranslatation(`workflow.blocks.${name}.name`, name);
   }
 
-  log.message = getTranslatation(`log.messages.${message}`, message);
+  log.message = getTranslatation(
+    { path: `log.messages.${message}`, params: log },
+    ''
+  );
 
   return log;
 }

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

@@ -45,12 +45,14 @@
         </ui-tabs>
         <div class="flex-grow"></div>
         <workflow-actions
+          :workflow="workflow"
           :is-data-changed="state.isDataChanged"
           @showModal="(state.modalName = $event), (state.showModal = true)"
           @save="saveWorkflow"
           @export="exportWorkflow(workflow)"
           @execute="executeWorkflow"
           @rename="renameWorkflow"
+          @update="updateWorkflow"
           @delete="deleteWorkflow"
         />
       </div>

+ 2 - 2
src/popup/App.vue

@@ -15,8 +15,8 @@ const retrieved = ref(false);
 onMounted(async () => {
   try {
     await store.dispatch('retrieve');
-    await loadLocaleMessages(store.state.locale, 'popup');
-    await setI18nLanguage(store.state.locale);
+    await loadLocaleMessages(store.state.settings.locale, 'popup');
+    await setI18nLanguage(store.state.settings.locale);
 
     retrieved.value = true;
   } catch (error) {

+ 8 - 6
src/popup/pages/Home.vue

@@ -44,6 +44,7 @@
       :key="workflow.id"
       :workflow="workflow"
       @details="openDashboard(`/workflows/${$event.id}`)"
+      @update="updateWorkflow(workflow.id, $event)"
       @execute="executeWorkflow"
       @rename="renameWorkflow"
       @delete="deleteWorkflow"
@@ -77,6 +78,12 @@ const workflows = computed(() =>
 function executeWorkflow(workflow) {
   sendMessage('workflow:execute', workflow, 'background');
 }
+function updateWorkflow(id, data) {
+  return Workflow.update({
+    where: id,
+    data,
+  });
+}
 function renameWorkflow({ id, name }) {
   dialog.prompt({
     title: t('home.workflow.rename'),
@@ -84,12 +91,7 @@ function renameWorkflow({ id, name }) {
     okText: t('common.rename'),
     inputValue: name,
     onConfirm: (newName) => {
-      Workflow.update({
-        where: id,
-        data: {
-          name: newName,
-        },
-      });
+      updateWorkflow(id, { name: newName });
     },
   });
 }