Przeglądaj źródła

feat: add option to executed block on webpage

Ahmad Kholid 3 lat temu
rodzic
commit
4a09a88a5a

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

@@ -7,6 +7,7 @@ async function interactionHandler(block, { refData }) {
     ...block,
     refData,
     frameSelector: this.frameSelector,
+    executedBlockOnWeb: this.workflow.settings?.executedBlockOnWeb,
   };
 
   try {

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

@@ -221,6 +221,7 @@ class WorkflowEngine {
     }
 
     this.currentBlock = block;
+    this.referenceData.prevBlockData = prevBlockData;
 
     await this.states.update(this.id, { state: this.state });
     this.dispatchEvent('update', { state: this.state });
@@ -248,9 +249,9 @@ class WorkflowEngine {
       });
 
       this.addLogHistory({
-        type: 'success',
         name: block.name,
         logId: result.logId,
+        type: result.status || 'success',
         duration: Math.round(Date.now() - startExecutedTime),
       });
 
@@ -308,6 +309,7 @@ class WorkflowEngine {
   get state() {
     const keys = [
       'history',
+      'columns',
       'activeTab',
       'isUsingProxy',
       'currentBlock',

+ 6 - 2
src/components/newtab/workflow/WorkflowSettings.vue

@@ -30,7 +30,11 @@
     </div>
     <div class="flex mt-6">
       <ui-switch v-model="settings.saveLog" class="mr-4" />
-      <p>Save log</p>
+      <p>{{ t('workflow.settings.saveLog') }}</p>
+    </div>
+    <div class="flex mt-6">
+      <ui-switch v-model="settings.executedBlockOnWeb" class="mr-4" />
+      <p>{{ t('workflow.settings.executedBlockOnWeb') }}</p>
     </div>
   </div>
 </template>
@@ -62,8 +66,8 @@ const onError = [
 const settings = reactive({
   blockDelay: 0,
   saveLog: true,
-  timeout: 120000,
   onError: 'stop-workflow',
+  executedBlockOnWeb: false,
 });
 
 watch(

+ 99 - 0
src/content/executed-block.js

@@ -0,0 +1,99 @@
+import { tasks } from '@/utils/shared';
+
+function generateElement(block) {
+  return `
+    <div style="display: flex; align-items: center">
+      <svg
+        xmlns="http://www.w3.org/2000/svg"
+        id="spinner"
+        fill="transparent"
+        width="24"
+        height="24"
+        viewBox="0 0 24 24"
+      >
+        <circle
+          class="opacity-25"
+          cx="12"
+          cy="12"
+          r="10"
+          stroke="currentColor"
+          stroke-width="4"
+        ></circle>
+        <path
+          class="opacity-75"
+          fill="currentColor"
+          d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
+        ></path>
+      </svg>
+      <p id="block-name">${block.name}</p>
+    </div>
+  `;
+}
+
+export default function (data, enable) {
+  if (!enable) {
+    return () => {};
+  }
+
+  const block = tasks[data.name];
+  let container = document.querySelector('.automa-executed-block');
+
+  if (!container) {
+    container = document.createElement('div');
+    container.classList.add('automa-executed-block');
+    document.body.appendChild(container);
+
+    const style = document.createElement('style');
+    style.classList.add('automa-executed-block');
+    style.innerHTML = `
+      @keyframes spin {
+        from {
+          transform: rotate(0deg);
+        }
+        to {
+          transform: rotate(360deg);
+        }
+      }
+
+      .automa-executed-block .opacity-25 {
+        opacity: 0.25;
+      }
+      .automa-executed-block .opacity-75 {
+        opacity: 0.75;
+      }
+      .automa-executed-block {
+        color: #18181b;
+        width: 250px;
+        position: fixed;
+        border-radius: 12px;
+        bottom: 12px;
+        right: 12px;
+        padding: 14px;
+        background-color: white;
+        font-size: 16px;
+        font-family: sans-serif;
+        box-shadow: box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+        z-index: 99999
+      }
+      .automa-executed-block #spinner {
+        color: currentColor;
+        display: inline-block;
+        animation: spin 1s linear infinite;
+      }
+      .automa-executed-block p {
+        margin: 0;
+        padding: 0;
+        margin-left: 8px;
+      }
+    `;
+    document.body.appendChild(style);
+  }
+  container.innerHTML = generateElement(block);
+
+  return () => {
+    const elements = document.querySelectorAll('.automa-executed-block');
+    elements.forEach((el) => {
+      el.remove();
+    });
+  };
+}

+ 23 - 9
src/content/index.js

@@ -1,6 +1,7 @@
 import browser from 'webextension-polyfill';
 import { toCamelCase } from '@/utils/helper';
 import elementSelector from './element-selector';
+import executedBlock from './executed-block';
 import blocksHandler from './blocks-handler';
 
 (() => {
@@ -9,18 +10,31 @@ import blocksHandler from './blocks-handler';
   window.isAutomaInjected = true;
 
   browser.runtime.onMessage.addListener((data) => {
-    if (data.isBlock) {
-      const handler = blocksHandler[toCamelCase(data.name)];
+    return new Promise((resolve, reject) => {
+      if (data.isBlock) {
+        const removeExecutedBlock = executedBlock(
+          data,
+          data.executedBlockOnWeb
+        );
 
-      if (handler) {
-        return handler(data);
-      }
-      console.error(`"${data.name}" doesn't have a handler`);
+        const handler = blocksHandler[toCamelCase(data.name)];
+
+        if (handler) {
+          handler(data)
+            .then((result) => {
+              removeExecutedBlock();
+              resolve(result);
+            })
+            .catch(reject);
 
-      return Promise.resolve('');
-    }
+          return;
+        }
+        console.error(`"${data.name}" doesn't have a handler`);
+
+        resolve('');
+        return;
+      }
 
-    return new Promise((resolve) => {
       if (data.type === 'content-script-exists') {
         resolve(true);
       } else if (data.type === 'select-element') {

+ 14 - 0
src/content/services/index.js

@@ -0,0 +1,14 @@
+import browser from 'webextension-polyfill';
+import webService from './web-service';
+import shortcutListener from './shortcut-listener';
+
+(async () => {
+  try {
+    const { workflows } = await browser.storage.local.get('workflows');
+
+    await webService(workflows);
+    await shortcutListener(workflows);
+  } catch (error) {
+    console.error(error);
+  }
+})();

+ 55 - 0
src/content/services/shortcut-listener.js

@@ -0,0 +1,55 @@
+import Mousetrap from 'mousetrap';
+import browser from 'webextension-polyfill';
+import { sendMessage } from '@/utils/message';
+
+Mousetrap.prototype.stopCallback = function () {
+  return false;
+};
+
+function getTriggerBlock(workflow) {
+  const drawflow = JSON.parse(workflow?.drawflow || '{}');
+
+  if (!drawflow?.drawflow?.Home?.data) return null;
+
+  const blocks = Object.values(drawflow.drawflow.Home.data);
+  const trigger = blocks.find(({ name }) => name === 'trigger');
+
+  return trigger;
+}
+
+export default async function (workflows) {
+  try {
+    const { shortcuts } = await browser.storage.local.get('shortcuts');
+    const shortcutsArr = Object.entries(shortcuts || {});
+
+    if (shortcutsArr.length === 0) return;
+
+    const keyboardShortcuts = shortcutsArr.reduce((acc, [id, value]) => {
+      const workflow = [...workflows].find((item) => item.id === id);
+
+      (acc[value] = acc[value] || []).push({
+        id,
+        workflow,
+        activeInInput: getTriggerBlock(workflow)?.data?.activeInInput,
+      });
+
+      return acc;
+    }, {});
+
+    Mousetrap.bind(Object.keys(keyboardShortcuts), ({ target }, command) => {
+      const isInputElement =
+        ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.tagName) ||
+        target?.contentEditable === 'true';
+
+      keyboardShortcuts[command].forEach((item) => {
+        if (!item.activeInInput && isInputElement) return;
+
+        sendMessage('workflow:execute', item.workflow, 'background');
+      });
+
+      return true;
+    });
+  } catch (error) {
+    console.error(error);
+  }
+}

+ 9 - 62
src/content/shortcut.js → src/content/services/web-service.js

@@ -1,26 +1,10 @@
 import { openDB } from 'idb';
 import { nanoid } from 'nanoid';
-import Mousetrap from 'mousetrap';
-import browser from 'webextension-polyfill';
 import secrets from 'secrets';
+import browser from 'webextension-polyfill';
 import { objectHasKey } from '@/utils/helper';
 import { sendMessage } from '@/utils/message';
 
-Mousetrap.prototype.stopCallback = function () {
-  return false;
-};
-
-function getTriggerBlock(workflow) {
-  const drawflow = JSON.parse(workflow?.drawflow || '{}');
-
-  if (!drawflow?.drawflow?.Home?.data) return null;
-
-  const blocks = Object.values(drawflow.drawflow.Home.data);
-  const trigger = blocks.find(({ name }) => name === 'trigger');
-
-  return trigger;
-}
-
 function initWebListener() {
   const listeners = {};
 
@@ -38,6 +22,7 @@ function initWebListener() {
 
   return { on };
 }
+
 async function listenWindowMessage(workflows) {
   try {
     if (secrets?.webOrigin !== window.location.origin) return;
@@ -78,49 +63,11 @@ async function listenWindowMessage(workflows) {
   }
 }
 
-(async () => {
-  try {
-    const { shortcuts, workflows } = await browser.storage.local.get([
-      'shortcuts',
-      'workflows',
-    ]);
-    const shortcutsArr = Object.entries(shortcuts || {});
-
-    listenWindowMessage(workflows);
-
-    document.body.setAttribute(
-      'data-atm-ext-installed',
-      browser.runtime.getManifest().version
-    );
+export default async function (workflows) {
+  await listenWindowMessage(workflows);
 
-    if (shortcutsArr.length === 0) return;
-
-    const keyboardShortcuts = shortcutsArr.reduce((acc, [id, value]) => {
-      const workflow = [...workflows].find((item) => item.id === id);
-
-      (acc[value] = acc[value] || []).push({
-        id,
-        workflow,
-        activeInInput: getTriggerBlock(workflow)?.data?.activeInInput,
-      });
-
-      return acc;
-    }, {});
-
-    Mousetrap.bind(Object.keys(keyboardShortcuts), ({ target }, command) => {
-      const isInputElement =
-        ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.tagName) ||
-        target?.contentEditable === 'true';
-
-      keyboardShortcuts[command].forEach((item) => {
-        if (!item.activeInInput && isInputElement) return;
-
-        sendMessage('workflow:execute', item.workflow, 'background');
-      });
-
-      return true;
-    });
-  } catch (error) {
-    console.error(error);
-  }
-})();
+  document.body.setAttribute(
+    'data-atm-ext-installed',
+    browser.runtime.getManifest().version
+  );
+}

+ 3 - 1
src/locales/en/newtab.json

@@ -14,7 +14,7 @@
     },
     "menu": {
       "general": "General"
-    }
+    },
   },
   "workflow": {
     "import": "Import workflow",
@@ -60,6 +60,8 @@
         "title": "Block delay (milliseconds)",
         "description": "Add delay before executing each of the blocks"
       },
+      "saveLog": "Save workflow log",
+      "executedBlockOnWeb": "Show executed block on web page"
     }
   },
   "collection": {

+ 1 - 1
src/manifest.json

@@ -20,7 +20,7 @@
         "<all_urls>"
       ],
       "js": [
-        "shortcut.bundle.js"
+        "services.bundle.js"
       ],
       "run_at": "document_end",
       "all_frames": false

+ 1 - 1
src/models/workflow.js

@@ -26,8 +26,8 @@ class Workflow extends Model {
       settings: this.attr({
         blockDelay: 0,
         saveLog: true,
-        timeout: 120000,
         onError: 'stop-workflow',
+        executedBlockOnWeb: false,
       }),
       logs: this.hasMany(Log, 'workflowId'),
     };

+ 0 - 1
src/utils/reference-data/mustache-replacer.js

@@ -31,7 +31,6 @@ export default function ({ str, data, block }) {
       const { dataKey, path } = keyParser(key);
       result = getObjectPath(data[dataKey], path) ?? match;
     }
-
     if (block.name === 'webhook') {
       return JSON.stringify(result);
     }

+ 2 - 2
webpack.config.js

@@ -43,7 +43,7 @@ const options = {
     popup: path.join(__dirname, 'src', 'popup', 'index.js'),
     background: path.join(__dirname, 'src', 'background', 'index.js'),
     contentScript: path.join(__dirname, 'src', 'content', 'index.js'),
-    shortcut: path.join(__dirname, 'src', 'content', 'shortcut.js'),
+    services: path.join(__dirname, 'src', 'content', 'services', 'index.js'),
     elementSelector: path.join(
       __dirname,
       'src',
@@ -53,7 +53,7 @@ const options = {
     ),
   },
   chromeExtensionBoilerplate: {
-    notHotReload: ['contentScript', 'shortcut', 'elementSelector'],
+    notHotReload: ['contentScript', 'services', 'elementSelector'],
   },
   output: {
     path: path.resolve(__dirname, 'build'),