Ahmad Kholid 3 年之前
父節點
當前提交
040c385be6

+ 2 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "automa",
-  "version": "0.13.2",
+  "version": "0.14.1",
   "description": "An extension for automating your browser by connecting blocks",
   "license": "MIT",
   "repository": {
@@ -10,6 +10,7 @@
   "scripts": {
     "build": "node utils/build.js",
     "build:zip": "node utils/build-zip.js",
+    "build:prod": "yarn build && yarn build:zip",
     "dev": "node utils/webserver.js",
     "prettier": "prettier --write '**/*.{js,jsx,css,html}'",
     "lint": "eslint --ext .js,.vue --ignore-path .gitignore ."

+ 41 - 13
src/background/workflow-engine/blocks-handler/handler-close-tab.js

@@ -1,22 +1,52 @@
 import browser from 'webextension-polyfill';
 import { getBlockConnection } from '../helper';
 
-async function closeTab({ data, outputs }) {
+async function closeWindow(data, windowId) {
+  const windowIds = [];
+
+  if (data.allWindows) {
+    const windows = await browser.windows.getAll();
+
+    windows.forEach(({ id }) => {
+      windowIds.push(id);
+    });
+  } else {
+    let currentWindowId;
+
+    if (windowId && typeof windowId === 'number') {
+      currentWindowId = windowId;
+    } else {
+      currentWindowId = (await browser.windows.getCurrent()).id;
+    }
+
+    windowIds.push(currentWindowId);
+  }
+
+  await Promise.allSettled(windowIds.map((id) => browser.windows.remove(id)));
+}
+
+async function closeTab(data, tabId) {
+  let tabIds;
+
+  if (data.activeTab && tabId) {
+    tabIds = tabId;
+  } else if (data.url) {
+    tabIds = (await browser.tabs.query({ url: data.url })).map((tab) => tab.id);
+  }
+
+  if (tabIds) await browser.tabs.remove(tabIds);
+}
+
+export default async function ({ data, outputs }) {
   const nextBlockId = getBlockConnection({ outputs });
 
   try {
-    let tabIds;
-
-    if (data.activeTab && this.activeTab.id) {
-      tabIds = this.activeTab.id;
-    } else if (data.url) {
-      tabIds = (await browser.tabs.query({ url: data.url })).map(
-        (tab) => tab.id
-      );
+    if (data.closeType === 'window') {
+      await closeWindow(data, this.windowId);
+    } else {
+      await closeTab(data, this.activeTab.id);
     }
 
-    if (tabIds) await browser.tabs.remove(tabIds);
-
     return {
       nextBlockId,
       data: '',
@@ -27,5 +57,3 @@ async function closeTab({ data, outputs }) {
     throw error;
   }
 }
-
-export default closeTab;

+ 1 - 1
src/components/block/BlockConditions.vue

@@ -87,7 +87,7 @@ function editBlock() {
 }
 function addConditionEmit({ id }) {
   if (id !== block.id) return;
-  console.log(block);
+
   const { length } = block.data.conditions;
 
   if (length >= 10) return;

+ 58 - 14
src/components/newtab/workflow/WorkflowBuilder.vue

@@ -115,22 +115,41 @@ export default {
       position: {},
     });
 
+    const prevSelectedEl = {
+      output: null,
+      connection: null,
+    };
+    const isOutputEl = (el) => el.classList.contains('output');
     const isConnectionEl = (el) =>
       el.matches('path.main-path') ||
       el.parentElement.classList.contains('connection');
 
-    let prevSelectedElement = null;
-    function handleDragOver({ target }) {
-      const dropInConnection = isConnectionEl(target);
+    function toggleHoverClass({ target, name, active, classes }) {
+      const prev = prevSelectedEl[name];
 
-      if (dropInConnection) {
-        if (prevSelectedElement !== target)
-          target.classList.toggle('selected', true);
-      } else if (prevSelectedElement) {
-        prevSelectedElement.classList.toggle('selected', false);
+      if (active) {
+        if (prev === target) return;
+
+        target.classList.toggle(classes, true);
+      } else if (prev) {
+        prev.classList.toggle(classes, false);
       }
 
-      prevSelectedElement = target;
+      prevSelectedEl[name] = target;
+    }
+    function handleDragOver({ target }) {
+      toggleHoverClass({
+        target,
+        name: 'connection',
+        classes: 'selected',
+        active: isConnectionEl(target),
+      });
+      toggleHoverClass({
+        target,
+        name: 'output',
+        classes: 'ring-4',
+        active: isOutputEl(target),
+      });
     }
     function dropHandler({ dataTransfer, clientX, clientY, target }) {
       const block = JSON.parse(dataTransfer.getData('block') || null);
@@ -162,17 +181,15 @@ export default {
         block.id,
         block.inputs,
         block.outputs,
-        xPosition,
-        yPosition,
+        xPosition + 25,
+        yPosition - 25,
         block.id,
         block.data,
         block.component,
         'vue'
       );
 
-      const dropInConnection = isConnectionEl(target);
-
-      if (dropInConnection) {
+      if (isConnectionEl(target)) {
         target.classList.remove('selected');
 
         const classes = target.parentElement.classList.toString();
@@ -212,6 +229,33 @@ export default {
         } catch (error) {
           // Do nothing
         }
+      } else if (isOutputEl(target)) {
+        prevSelectedEl.output?.classList.remove('ring-4');
+
+        const targetBlockId = target
+          .closest('.drawflow-node')
+          .id.replace(/node-/, '');
+        const outputClass = target.classList[1];
+        const blockData = editor.value.getNodeFromId(targetBlockId);
+        const { connections } = blockData.outputs[outputClass];
+
+        if (connections[0]) {
+          const { output, node } = connections[0];
+
+          editor.value.removeSingleConnection(
+            targetBlockId,
+            node,
+            outputClass,
+            output
+          );
+        }
+
+        editor.value.addConnection(
+          targetBlockId,
+          blockId,
+          outputClass,
+          'input_1'
+        );
       }
 
       emitter.emit('editor:data-changed');

+ 50 - 24
src/components/newtab/workflow/edit/EditCloseTab.vue

@@ -1,31 +1,56 @@
 <template>
   <div class="mb-2 mt-4">
-    <div class="mb-2">
-      <ui-checkbox
-        :model-value="data.activeTab"
-        @change="updateData({ activeTab: $event })"
-      >
-        {{ t('workflow.blocks.close-tab.activeTab') }}
-      </ui-checkbox>
-    </div>
-    <ui-input
-      v-if="!data.activeTab"
-      :model-value="data.url"
-      placeholder="http://example.com/*"
-      @change="updateData({ url: $event })"
+    <ui-select
+      :model-value="data.closeType"
+      placeholder="Close"
+      class="w-full mb-4"
+      @change="updateData({ closeType: $event })"
     >
-      <template #label>
-        {{ t('workflow.blocks.close-tab.url') }}
-        <a
-          href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns"
-          target="_blank"
-          rel="noopener"
-          title="More info"
+      <option
+        v-for="type in types"
+        :key="type"
+        :value="type"
+        class="capitalize"
+      >
+        {{ type }}
+      </option>
+    </ui-select>
+    <template v-if="data.closeType === 'tab'">
+      <div class="mb-2">
+        <ui-checkbox
+          :model-value="data.activeTab"
+          @change="updateData({ activeTab: $event })"
         >
-          &#9432;
-        </a>
-      </template>
-    </ui-input>
+          {{ t('workflow.blocks.close-tab.activeTab') }}
+        </ui-checkbox>
+      </div>
+      <ui-input
+        v-if="!data.activeTab"
+        :model-value="data.url"
+        class="w-full"
+        placeholder="http://example.com/*"
+        @change="updateData({ url: $event })"
+      >
+        <template #label>
+          {{ t('workflow.blocks.close-tab.url') }}
+          <a
+            href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns"
+            target="_blank"
+            rel="noopener"
+            title="More info"
+          >
+            &#9432;
+          </a>
+        </template>
+      </ui-input>
+    </template>
+    <ui-checkbox
+      v-else
+      :model-value="data.allWindows"
+      @change="updateData({ allWindows: $event })"
+    >
+      {{ t('workflow.blocks.close-tab.allWindows') }}
+    </ui-checkbox>
   </div>
 </template>
 <script setup>
@@ -40,6 +65,7 @@ const props = defineProps({
 const emit = defineEmits(['update:data']);
 
 const { t } = useI18n();
+const types = ['tab', 'window'];
 
 function updateData(value) {
   emit('update:data', { ...props.data, ...value });

+ 1 - 5
src/components/newtab/workflow/edit/EditConditions.vue

@@ -54,7 +54,7 @@
   </div>
 </template>
 <script setup>
-import { ref, watch, onUnmounted } from 'vue';
+import { ref, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
 import emitter from '@/lib/mitt';
 
@@ -119,8 +119,4 @@ watch(
   },
   { deep: true }
 );
-
-onUnmounted(() => {
-  emitter.off('conditions-block:delete-cond', handleDelCondition);
-});
 </script>

+ 4 - 0
src/composable/shortcut.js

@@ -43,6 +43,10 @@ export const mapShortcuts = {
     id: 'editor:execute-workflow',
     combo: 'option+enter',
   },
+  'editor:toggle-sidebar': {
+    id: 'editor:toggle-sidebar',
+    combo: 'mod+[',
+  },
 };
 
 const os = navigator.appVersion.indexOf('Win') !== -1 ? 'win' : 'mac';

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

@@ -1,6 +1,8 @@
 import vRemixicon from 'v-remixicon';
 import {
   riHome5Line,
+  riSideBarLine,
+  riSideBarFill,
   riFolderZipLine,
   riHandHeartLine,
   riCompass3Line,
@@ -79,6 +81,8 @@ import {
 
 export const icons = {
   riHome5Line,
+  riSideBarLine,
+  riSideBarFill,
   riFolderZipLine,
   riHandHeartLine,
   riCompass3Line,

+ 4 - 3
src/locales/en/blocks.json

@@ -137,10 +137,11 @@
         "description": "Go forward to the next page"
       },
       "close-tab": {
-        "name": "Close tab",
+        "name": "Close tab/window",
         "description": "",
-        "activeTab": "Close activeTab",
-        "url": "URL or match pattern"
+        "url": "URL or match pattern",
+        "activeTab": "Close active tab",
+        "allWindows": "Close all windows"
       },
       "event-click": {
         "name": "Click element",

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

@@ -25,6 +25,7 @@
     "rename": "Rename workflow",
     "add": "Add workflow",
     "clickToEnable": "Click to enable",
+    "toggleSidebar": "Toggle sidebar",
     "state": {
       "executeBy": "Executed by: \"{name}\""
     },

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

@@ -1,6 +1,7 @@
 <template>
   <div class="flex h-screen">
     <div
+      v-if="state.showSidebar"
       class="w-80 bg-white py-6 relative border-l border-gray-100 flex flex-col"
     >
       <workflow-edit-block
@@ -21,6 +22,20 @@
           v-model="activeTab"
           class="border-none px-2 rounded-lg h-full space-x-1 bg-white"
         >
+          <button
+            v-tooltip="
+              `${t('workflow.toggleSidebar')} (${
+                shortcut['editor:toggle-sidebar'].readable
+              })`
+            "
+            class="text-gray-800"
+            style="margin-right: 6px"
+            @click="toggleSidebar"
+          >
+            <v-remixicon
+              :name="state.showSidebar ? 'riSideBarFill' : 'riSideBarLine'"
+            />
+          </button>
           <ui-tab value="editor">{{ t('common.editor') }}</ui-tab>
           <ui-tab value="logs">{{ t('common.log', 2) }}</ui-tab>
           <ui-tab value="running" class="flex items-center">
@@ -135,7 +150,7 @@
   </ui-modal>
 </template>
 <script setup>
-/* eslint-disable consistent-return */
+/* eslint-disable consistent-return, no-use-before-define */
 import {
   computed,
   reactive,
@@ -151,6 +166,7 @@ import { useI18n } from 'vue-i18n';
 import defu from 'defu';
 import emitter from '@/lib/mitt';
 import { useDialog } from '@/composable/dialog';
+import { useShortcut } from '@/composable/shortcut';
 import { tasks } from '@/utils/shared';
 import { sendMessage } from '@/utils/message';
 import { debounce, isObject } from '@/utils/helper';
@@ -173,6 +189,7 @@ const store = useStore();
 const route = useRoute();
 const router = useRouter();
 const dialog = useDialog();
+const shortcut = useShortcut('editor:toggle-sidebar', toggleSidebar);
 
 const workflowId = route.params.id;
 const workflowModals = {
@@ -199,6 +216,7 @@ const state = reactive({
   blockData: {},
   modalName: '',
   showModal: false,
+  showSidebar: true,
   isEditBlock: false,
   isDataChanged: false,
 });
@@ -248,6 +266,10 @@ function deleteLog(logId) {
     store.dispatch('saveToStorage', 'logs');
   });
 }
+function toggleSidebar() {
+  state.showSidebar = !state.showSidebar;
+  localStorage.setItem('workflow:sidebar', state.showSidebar);
+}
 function deleteBlock(id) {
   if (!state.isEditBlock) return;
 
@@ -362,6 +384,9 @@ onMounted(() => {
     router.push('/workflows');
   }
 
+  state.showSidebar =
+    JSON.parse(localStorage.getItem('workflow:sidebar')) ?? true;
+
   window.onbeforeunload = () => {
     if (state.isDataChanged) {
       return t('message.notSaved');

+ 3 - 1
src/utils/shared.js

@@ -139,7 +139,7 @@ export const tasks = {
     data: {},
   },
   'close-tab': {
-    name: 'Close tab',
+    name: 'Close tab/window',
     icon: 'riCloseCircleLine',
     component: 'BlockBasic',
     category: 'browser',
@@ -151,6 +151,8 @@ export const tasks = {
     data: {
       url: '',
       activeTab: true,
+      closeType: 'tab',
+      allWindows: false,
     },
   },
   'take-screenshot': {