Browse Source

feat: add workflows paramters popup

Ahmad Kholid 2 years ago
parent
commit
d4cc437f56

+ 0 - 9
src/background/index.js

@@ -13,7 +13,6 @@ import {
   registerWorkflowTrigger,
 } from '../utils/workflowTrigger';
 import WorkflowState from './WorkflowState';
-import CollectionEngine from './collectionEngine';
 import WorkflowEngine from './workflowEngine/engine';
 import blocksHandler from './workflowEngine/blocksHandler';
 import WorkflowLogger from './WorkflowLogger';
@@ -589,14 +588,6 @@ message.on('get:tab-screenshot', (options) =>
   browser.tabs.captureVisibleTab(options)
 );
 
-message.on('collection:execute', (collection) => {
-  const engine = new CollectionEngine(collection, {
-    states: workflow.states,
-    logger: workflow.logger,
-  });
-  engine.init();
-});
-
 message.on('workflow:execute', (workflowData, sender) => {
   if (workflowData.includeTabId) {
     if (!workflowData.options) workflowData.options = {};

+ 28 - 0
src/background/workflowEngine/engine.js

@@ -109,6 +109,34 @@ class WorkflowEngine {
         return;
       }
 
+      const checkParams = this.options?.checkParams ?? true;
+      const hasParams = triggerBlock.data.parameters?.length > 0;
+      if (checkParams && hasParams) {
+        this.eventListeners = {};
+
+        const paramUrl = browser.runtime.getURL('params.html');
+        const tabs = await browser.tabs.query({});
+        const paramTab = tabs.find((tab) => tab.url?.includes(paramUrl));
+
+        if (paramTab) {
+          browser.tabs.sendMessage(paramTab.id, {
+            name: 'workflow:params',
+            data: this.workflow,
+          });
+
+          browser.windows.update(paramTab.windowId, { focused: true });
+        } else {
+          browser.windows.create({
+            type: 'popup',
+            width: 480,
+            url: browser.runtime.getURL(
+              `/params.html?workflowId=${this.workflow.id}`
+            ),
+          });
+        }
+        return;
+      }
+
       this.triggerBlockId = triggerBlock.id;
 
       this.blocks = nodes.reduce((acc, node) => {

+ 2 - 6
src/components/newtab/workflow/edit/EditTrigger.vue

@@ -35,12 +35,8 @@
       title="Parameters"
       content-class="max-w-2xl"
     >
-      <p class="leading-tight">
-        These parameters will be used when the workflow is executed from the
-        command palette
-      </p>
       <ul
-        class="space-y-2 mt-2 overflow-auto scroll"
+        class="space-y-2 mt-2 pb-1 overflow-auto scroll"
         style="max-height: calc(100vh - 15rem)"
       >
         <li
@@ -122,7 +118,7 @@ function updateData(value) {
 }
 function addParameter() {
   state.parameters.push({
-    name: 'Param',
+    name: 'param',
     type: 'string',
     placeholder: 'Text',
   });

+ 1 - 1
src/content/commandPalette/App.vue

@@ -197,8 +197,8 @@ function clearParamsState() {
 function sendExecuteCommand(workflow, options = {}) {
   const workflowData = {
     ...workflow,
-    options,
     includeTabId: true,
+    options: { ...options, checkParams: false },
   };
 
   sendMessage('workflow:execute', workflowData, 'background');

+ 191 - 0
src/params/App.vue

@@ -0,0 +1,191 @@
+<template>
+  <div class="w-full flex flex-col h-full max-w-lg mx-auto dark:text-gray-100">
+    <nav class="flex items-center w-full p-4 border-b mb-4">
+      <span class="p-1 rounded-full bg-box-transparent dark:bg-none">
+        <img src="@/assets/svg/logo.svg" class="w-10" />
+      </span>
+      <p class="font-semibold text-lg ml-4">Automa</p>
+    </nav>
+    <div class="px-4 pb-4 flex-1 overflow-auto scroll">
+      <p class="text-gray-600 my-4 dark:text-gray-200">
+        Input these workflows parameters before it runs.
+      </p>
+      <ui-expand
+        v-for="(workflow, index) in sortedWorkflows"
+        :key="index"
+        :model-value="true"
+        append-icon
+        header-class="flex items-center text-left p-4 w-full rounded-lg"
+        class="bg-white mb-4 dark:bg-gray-800 rounded-lg"
+      >
+        <template #header>
+          <ui-img
+            v-if="workflow.data.icon?.startsWith('http')"
+            :src="workflow.data.icon"
+            class="overflow-hidden rounded-lg"
+            style="height: 40px; width: 40px"
+            alt="Can not display"
+          />
+          <span v-else class="p-2 rounded-lg bg-box-transparent">
+            <v-remixicon :name="workflow.data.icon" />
+          </span>
+          <div class="ml-4 flex-1 ml-2 overflow-hidden">
+            <p
+              class="text-overflow leading-tight mr-4 text-gray-600 dark:text-gray-200"
+            >
+              {{ workflow.data.name }}
+            </p>
+            <p class="leading-tight text-overflow">
+              {{ workflow.data.description }}
+            </p>
+          </div>
+        </template>
+        <div class="px-4 pb-4">
+          <div class="space-y-2">
+            <ui-input
+              v-for="(param, paramIdx) in workflow.params"
+              :key="paramIdx"
+              v-model="param.value"
+              :type="param.inputType"
+              :label="param.name"
+              :placeholder="param.placeholder"
+              class="w-full"
+              @keyup.enter="runWorkflow(index, workflow)"
+            />
+          </div>
+          <div class="flex items-center mt-6">
+            <p>{{ dayjs(workflow.addedDate).fromNow() }}</p>
+            <div class="flex-grow" />
+            <ui-button class="mr-4" @click="deleteWorkflow(index)">
+              Cancel
+            </ui-button>
+            <ui-button variant="accent" @click="runWorkflow(index, workflow)">
+              <v-remixicon name="riPlayLine" class="mr-2 -ml-1" />
+              Run
+            </ui-button>
+          </div>
+        </div>
+      </ui-expand>
+    </div>
+  </div>
+</template>
+<script setup>
+import { onMounted, ref, computed } from 'vue';
+import browser from 'webextension-polyfill';
+import dayjs from '@/lib/dayjs';
+import { useTheme } from '@/composable/theme';
+
+const theme = useTheme();
+theme.init();
+
+const workflows = ref([]);
+
+const sortedWorkflows = computed(() =>
+  workflows.value.slice().sort((a, b) => b.addedDate - a.addedDate)
+);
+
+const flattenTeamWorkflows = (items) => Object.values(Object.values(items)[0]);
+
+async function findWorkflow(workflowId) {
+  if (!workflowId) return null;
+
+  if (workflowId.startsWith('team')) {
+    const { teamWorkflows } = await browser.storage.local.get('teamWorkflows');
+    if (!teamWorkflows) return null;
+
+    const teamWorkflowsArr = flattenTeamWorkflows(teamWorkflows);
+
+    return teamWorkflowsArr.find((item) => item.id === workflowId);
+  }
+
+  const { workflows: localWorkflows, workflowHosts } =
+    await browser.storage.local.get(['workflows', 'workflowHosts']);
+  let workflow = Array.isArray(localWorkflows)
+    ? localWorkflows.find(({ id }) => id === workflowId)
+    : localWorkflows[workflowId];
+
+  if (!workflow) {
+    workflow = Object.values(workflowHosts || {}).find(
+      ({ hostId }) => hostId === workflowId
+    );
+
+    if (workflow) workflow.id = workflow.hostId;
+  }
+
+  return workflow;
+}
+function deleteWorkflow(index) {
+  workflows.value.splice(index, 1);
+
+  if (workflows.value.length === 0) {
+    window.close();
+  }
+}
+async function addWorkflow(workflowId) {
+  try {
+    const workflow =
+      typeof workflowId === 'string'
+        ? await findWorkflow(workflowId)
+        : workflowId;
+    const triggerBlock = workflow.drawflow.nodes.find(
+      (node) => node.label === 'trigger'
+    );
+    if (!triggerBlock) return;
+
+    const params = triggerBlock.data.parameters.map((param) => ({
+      ...param,
+      value: '',
+      inputType: param.type === 'string' ? 'text' : 'number',
+    }));
+    workflows.value.push({
+      params,
+      data: workflow,
+      addedDate: Date.now(),
+    });
+  } catch (error) {
+    console.error(error);
+  }
+}
+function runWorkflow(index, { data, params }) {
+  const getParamVal = {
+    string: (str) => str,
+    number: (num) => (Number.isNaN(+num) ? 0 : +num),
+  };
+
+  const variables = params.reduce((acc, param) => {
+    const value = getParamVal[param.type](param.value);
+    acc[param.name] = value;
+
+    return acc;
+  }, {});
+  const payload = {
+    ...data,
+    options: {
+      checkParams: false,
+      data: { variables },
+    },
+  };
+
+  browser.runtime
+    .sendMessage({
+      name: 'background--workflow:execute',
+      data: payload,
+    })
+    .then(() => {
+      deleteWorkflow(index);
+    });
+}
+
+browser.runtime.onMessage.addListener(({ name, data }) => {
+  if (name !== 'workflow:params') return;
+
+  addWorkflow(data);
+});
+
+onMounted(() => {
+  const query = new URLSearchParams(window.location.search);
+  const workflowId = query.get('workflowId');
+
+  if (workflowId) addWorkflow(workflowId);
+});
+</script>

+ 11 - 0
src/params/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <title></title>
+  </head>
+
+  <body>
+    <div id="app" class="scroll"></div>
+  </body>
+</html>

+ 11 - 0
src/params/index.js

@@ -0,0 +1,11 @@
+import { createApp } from 'vue';
+import App from './App.vue';
+import compsUi from '../lib/compsUi';
+import vRemixicon, { icons } from '../lib/vRemixicon';
+import '../assets/css/tailwind.css';
+import '../assets/css/fonts.css';
+import '../assets/css/flow.css';
+
+createApp(App).use(compsUi).use(vRemixicon, icons).mount('#app');
+
+if (module.hot) module.hot.accept();

+ 7 - 0
webpack.config.js

@@ -41,6 +41,7 @@ const options = {
   entry: {
     newtab: path.join(__dirname, 'src', 'newtab', 'index.js'),
     popup: path.join(__dirname, 'src', 'popup', 'index.js'),
+    params: path.join(__dirname, 'src', 'params', 'index.js'),
     background: path.join(__dirname, 'src', 'background', 'index.js'),
     contentScript: path.join(__dirname, 'src', 'content', 'index.js'),
     recordWorkflow: path.join(
@@ -196,6 +197,12 @@ const options = {
       chunks: ['popup'],
       cache: false,
     }),
+    new HtmlWebpackPlugin({
+      template: path.join(__dirname, 'src', 'params', 'index.html'),
+      filename: 'params.html',
+      chunks: ['params'],
+      cache: false,
+    }),
     new webpack.DefinePlugin({
       __VUE_OPTIONS_API__: true,
       __VUE_PROD_DEVTOOLS__: false,