瀏覽代碼

feat(popup): add workflow list

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

+ 1 - 1
src/background/index.js

@@ -58,7 +58,7 @@ browser.alarms.onAlarm.addListener(({ name }) => {
 });
 
 browser.runtime.onInstalled.addListener((details) => {
-  if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
+  if (details.reason === 'install') {
     browser.storage.local.set({
       logs: [],
       workflows: [],

+ 9 - 4
src/background/workflow-engine.js

@@ -139,10 +139,10 @@ class WorkflowEngine {
       type: 'stop',
       name: 'Workflow is stopped',
     });
-    this.destroy();
+    this.destroy('stopped');
   }
 
-  async destroy() {
+  async destroy(status) {
     try {
       this.eventListeners = {};
       this.tabMessageListeners = {};
@@ -158,12 +158,17 @@ class WorkflowEngine {
       this.endedTimestamp = Date.now();
 
       if (!this.workflow.isTesting) {
-        const { logs = [] } = browser.storage.local.get('logs');
+        const { logs } = browser.storage.local.get('logs');
+        const { name, icon, id } = this.workflow;
 
         logs.push({
+          name,
+          icon,
+          status,
+          id: this.id,
+          workflowId: id,
           data: this.data,
           history: this.logs,
-          name: this.workflow.name,
           endedAt: this.endedTimestamp,
           startedAt: this.startedTimestamp,
         });

+ 35 - 4
src/components/popup/home/HomeWorkflowCard.vue

@@ -3,18 +3,49 @@
     class="w-full flex items-center space-x-2 hover:ring-2 hover:ring-gray-900"
   >
     <router-link to="/workflow/anu/edit" class="flex-1">
-      <p class="leading-tight">halo {{ 1 }}</p>
-      <p class="leading-none text-gray-500">3 days ago</p>
+      <p class="leading-tight">{{ workflow.name }}</p>
+      <p class="leading-none text-gray-500">
+        {{ dayjs(workflow.createdAt).fromNow() }}
+      </p>
     </router-link>
-    <router-link to="/workflow/anu" icon title="Execute">
+    <button title="Execute" @click="$emit('execute', workflow)">
       <v-remixicon name="riPlayLine" />
-    </router-link>
+    </button>
     <ui-popover class="h-6">
       <template #trigger>
         <button>
           <v-remixicon name="riMoreLine" />
         </button>
       </template>
+      <ui-list class="w-40 space-y-1">
+        <ui-list-item
+          v-for="item in menu"
+          :key="item.name"
+          v-close-popover
+          class="capitalize cursor-pointer"
+          @click="$emit(item.name, workflow)"
+        >
+          <v-remixicon :name="item.icon" class="mr-2 -ml-1" />
+          <span>{{ item.name }}</span>
+        </ui-list-item>
+      </ui-list>
     </ui-popover>
   </ui-card>
 </template>
+<script setup>
+import dayjs from '@/lib/dayjs';
+
+defineProps({
+  workflow: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+defineEmits(['execute', 'rename', 'details', 'delete']);
+
+const menu = [
+  { name: 'details', icon: 'riExternalLinkLine' },
+  { name: 'rename', icon: 'riPencilLine' },
+  { name: 'delete', icon: 'riDeleteBin7Line' },
+];
+</script>

+ 1 - 1
src/content/element-selector/ElementSelector.ce.vue

@@ -91,7 +91,7 @@ function copySelector() {
   navigator.clipboard
     .writeText(element.selector)
     .then(() => {
-      console.log('Selector copied');
+      root.shadowRoot.querySelector('input')?.select();
     })
     .catch((error) => {
       console.error(error);

+ 7 - 19
src/newtab/App.vue

@@ -6,31 +6,19 @@
   <ui-dialog />
 </template>
 <script setup>
-import { onMounted, ref } from 'vue';
+import { ref } from 'vue';
 import { useStore } from 'vuex';
-import browser from 'webextension-polyfill';
 import AppSidebar from '@/components/newtab/app/AppSidebar.vue';
 
 const store = useStore();
-
 const retrieved = ref(false);
 
-onMounted(async () => {
-  try {
-    const data = await browser.storage.local.get(['workflows', 'tasks']);
-    const promises = Object.keys(data).map((entity) =>
-      store.dispatch('entities/create', {
-        entity,
-        data: data[entity],
-      })
-    );
-
-    console.log(await Promise.allSettled(promises));
-
+store
+  .dispatch('retrieve', ['workflows', 'logs'])
+  .then(() => {
     retrieved.value = true;
-  } catch (error) {
+  })
+  .catch(() => {
     retrieved.value = true;
-    console.error(error);
-  }
-});
+  });
 </script>

+ 18 - 1
src/popup/App.vue

@@ -1,6 +1,23 @@
 <template>
-  <router-view />
+  <router-view v-if="retrieved" />
+  <ui-dialog />
 </template>
+<script setup>
+import { ref } from 'vue';
+import { useStore } from 'vuex';
+
+const store = useStore();
+const retrieved = ref(false);
+
+store
+  .dispatch('retrieve')
+  .then(() => {
+    retrieved.value = true;
+  })
+  .catch(() => {
+    retrieved.value = true;
+  });
+</script>
 <style>
 body {
   height: 500px;

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

@@ -4,6 +4,7 @@
     class="flex dark placeholder-black text-white px-5 pt-8 mb-6 items-center"
   >
     <ui-input
+      v-model="query"
       autofocus
       prepend-icon="riSearch2Line"
       class="flex-1 search-input"
@@ -22,21 +23,93 @@
     </ui-button>
   </div>
   <div class="px-5 pb-5 space-y-2">
-    <home-workflow-card v-for="i in 10" :key="i" />
+    <ui-card v-if="Workflow.all().length === 0" class="text-center">
+      <img src="@/assets/svg/alien.svg" />
+      <p class="font-semibold">It looks like you don't have any workflows</p>
+      <ui-button variant="accent" class="mt-6" @click="openDashboard">
+        New workflow
+      </ui-button>
+    </ui-card>
+    <home-workflow-card
+      v-for="workflow in workflows"
+      :key="workflow.id"
+      :workflow="workflow"
+      @details="openDashboard(`/workflows/${$event.id}`)"
+      @execute="executeWorkflow"
+      @rename="renameWorkflow"
+      @delete="deleteWorkflow"
+    />
   </div>
 </template>
 <script setup>
+import { ref, computed } from 'vue';
 import browser from 'webextension-polyfill';
+import { useDialog } from '@/composable/dialog';
+import { sendMessage } from '@/utils/message';
+import Workflow from '@/models/workflow';
 import HomeWorkflowCard from '@/components/popup/home/HomeWorkflowCard.vue';
 
-function openDashboard() {
-  const newTabURL = chrome.runtime.getURL('/newtab.html');
+const dialog = useDialog();
 
-  browser.tabs.query({ url: newTabURL }).then(([tab]) => {
-    if (tab) browser.tabs.update(tab.id, { active: true });
-    else browser.tabs.create({ url: newTabURL, active: true });
+const query = ref('');
+const workflows = computed(() =>
+  Workflow.query()
+    .where(({ name }) =>
+      name.toLocaleLowerCase().includes(query.value.toLocaleLowerCase())
+    )
+    .orderBy('createdAt', 'asc')
+    .get()
+);
+
+function executeWorkflow(workflow) {
+  sendMessage('workflow:execute', workflow, 'background');
+}
+function renameWorkflow({ id, name }) {
+  dialog.prompt({
+    title: 'Rename workflow',
+    placeholder: 'Workflow name',
+    okText: 'Rename',
+    inputValue: name,
+    onConfirm: (newName) => {
+      Workflow.update({
+        where: id,
+        data: {
+          name: newName,
+        },
+      });
+    },
+  });
+}
+function deleteWorkflow({ id, name }) {
+  dialog.confirm({
+    title: 'Delete workflow',
+    okVariant: 'danger',
+    body: `Are you sure you want to delete "${name}" workflow?`,
+    onConfirm: () => {
+      Workflow.delete(id);
+    },
   });
 }
+function openDashboard(url) {
+  const tabOptions = {
+    active: true,
+    url: browser.runtime.getURL(
+      `/newtab.html#${typeof url === 'string' ? url : ''}`
+    ),
+  };
+
+  browser.tabs
+    .query({ url: browser.runtime.getURL('/newtab.html') })
+    .then(([tab]) => {
+      if (tab) {
+        browser.tabs.update(tab.id, tabOptions).then(() => {
+          browser.tabs.reload(tab.id);
+        });
+      } else {
+        browser.tabs.create(tabOptions);
+      }
+    });
+}
 async function selectElement() {
   const [tab] = await browser.tabs.query({ active: true });
 

+ 15 - 0
src/store/index.js

@@ -6,6 +6,21 @@ import * as models from '@/models';
 const store = createStore({
   plugins: [vuexORM(models)],
   actions: {
+    async retrieve({ dispatch }, keys = 'workflows') {
+      try {
+        const data = await browser.storage.local.get(keys);
+        const promises = Object.keys(data).map((entity) =>
+          dispatch('entities/create', {
+            entity,
+            data: data[entity],
+          })
+        );
+
+        await Promise.allSettled(promises);
+      } catch (error) {
+        console.error(error);
+      }
+    },
     saveToStorage({ getters }, key) {
       return new Promise((resolve, reject) => {
         if (!key) {

+ 0 - 20
tsconfig.json

@@ -1,20 +0,0 @@
-{
-  "compilerOptions": {
-    "target": "es5",
-    "lib": ["dom", "dom.iterable", "esnext"],
-    "allowJs": false,
-    "skipLibCheck": true,
-    "esModuleInterop": true,
-    "allowSyntheticDefaultImports": true,
-    "strict": true,
-    "forceConsistentCasingInFileNames": true,
-    "noFallthroughCasesInSwitch": true,
-    "module": "esnext",
-    "moduleResolution": "node",
-    "resolveJsonModule": true,
-    "noEmit": false,
-    "jsx": "react"
-  },
-  "include": ["src"],
-  "exclude": ["build", "node_modules"]
-}