Browse Source

feat(newtab): add running tab

Ahmad Kholid 3 years ago
parent
commit
c2ce26a6d3

+ 10 - 0
src/background/index.js

@@ -74,5 +74,15 @@ browser.runtime.onInstalled.addListener((details) => {
 const message = new MessageListener('background');
 const message = new MessageListener('background');
 
 
 message.on('workflow:execute', (workflow) => executeWorkflow(workflow));
 message.on('workflow:execute', (workflow) => executeWorkflow(workflow));
+message.on('workflow:stop', (item) => {
+  const workflow = runningWorkflows[item.id];
+  console.log(workflow, item, 'stop workflow');
+  if (!workflow) {
+    workflowState.delete(item.id);
+    return;
+  }
+
+  workflow.stop();
+});
 
 
 browser.runtime.onMessage.addListener(message.listener());
 browser.runtime.onMessage.addListener(message.listener());

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

@@ -32,6 +32,8 @@ function tabRemovedHandler(tabId) {
   if (tasks[this.currentBlock.name].category === 'interaction') {
   if (tasks[this.currentBlock.name].category === 'interaction') {
     this.destroy('error');
     this.destroy('error');
   }
   }
+
+  workflowState.update(this.id, this.state);
 }
 }
 function tabUpdatedHandler(tabId, changeInfo) {
 function tabUpdatedHandler(tabId, changeInfo) {
   const listener = this.tabUpdatedListeners[tabId];
   const listener = this.tabUpdatedListeners[tabId];
@@ -203,13 +205,22 @@ class WorkflowEngine {
   }
   }
 
 
   get state() {
   get state() {
-    const keys = ['tabId', 'isPaused', 'isDestroyed', 'currentBlock'];
-
-    return keys.reduce((acc, key) => {
+    const keys = [
+      'tabId',
+      'isPaused',
+      'isDestroyed',
+      'currentBlock',
+      'startedTimestamp',
+    ];
+    const state = keys.reduce((acc, key) => {
       acc[key] = this[key];
       acc[key] = this[key];
 
 
       return acc;
       return acc;
     }, {});
     }, {});
+
+    state.name = this.workflow.name;
+
+    return state;
   }
   }
 
 
   _blockHandler(block, prevBlockData) {
   _blockHandler(block, prevBlockData) {

+ 4 - 3
src/background/workflow-state.js

@@ -4,8 +4,8 @@ import browser from 'webextension-polyfill';
 async function updater(callback, id) {
 async function updater(callback, id) {
   try {
   try {
     const state = await this.get();
     const state = await this.get();
-    const index = id ? state.find((item) => item.id === id) : -1;
-    const items = callback(state, index || -1);
+    const index = id ? state.findIndex((item) => item.id === id) : -1;
+    const items = callback(state, index);
 
 
     await browser.storage.local.set({ workflowState: items });
     await browser.storage.local.set({ workflowState: items });
 
 
@@ -36,7 +36,7 @@ class WorkflowState {
 
 
   static add(id, data) {
   static add(id, data) {
     return updater.call(this, (items) => {
     return updater.call(this, (items) => {
-      items.push({ id, ...data });
+      items.unshift({ id, ...data });
 
 
       return items;
       return items;
     });
     });
@@ -60,6 +60,7 @@ class WorkflowState {
     return updater.call(
     return updater.call(
       this,
       this,
       (items, index) => {
       (items, index) => {
+        console.log('state index', index, id);
         if (index !== -1) items.splice(index, 1);
         if (index !== -1) items.splice(index, 1);
 
 
         return items;
         return items;

+ 72 - 0
src/components/newtab/workflow/WorkflowRunning.vue

@@ -0,0 +1,72 @@
+<template>
+  <div class="grid grid-cols-2 gap-4">
+    <ui-card v-for="item in data" :key="item">
+      <div class="flex items-center mb-4">
+        <div class="flex-1 text-overflow mr-4">
+          <p class="w-full mr-2 text-overflow">{{ item.state.name }}</p>
+          <p
+            class="w-full mr-2 text-gray-600 leading-tight text-overflow"
+            :title="`Started at: ${formatDate(
+              item.state.startedTimestamp,
+              'DD MMM, hh:mm A'
+            )}`"
+          >
+            {{ formatDate(item.state.startedTimestamp, 'relative') }}
+          </p>
+        </div>
+        <ui-button
+          v-if="item.state.tabId"
+          icon
+          class="mr-2"
+          title="Open tab"
+          @click="openTab(item.state.tabId)"
+        >
+          <v-remixicon name="riExternalLinkLine" />
+        </ui-button>
+        <ui-button variant="accent" @click="stopWorkflow(item)">
+          <v-remixicon name="riStopLine" class="mr-2 -ml-1" />
+          <span>Stop</span>
+        </ui-button>
+      </div>
+      <div class="flex items-center bg-box-transparent px-4 py-2 rounded-lg">
+        <template v-if="item.state.currentBlock">
+          <v-remixicon :name="getBlock(item).icon" />
+          <p class="flex-1 ml-2 mr-4">{{ getBlock(item).name }}</p>
+          <ui-spinner color="text-accnet" size="20" />
+        </template>
+        <p v-else>No block</p>
+      </div>
+    </ui-card>
+  </div>
+</template>
+<script setup>
+import browser from 'webextension-polyfill';
+import { sendMessage } from '@/utils/message';
+import { tasks } from '@/utils/shared';
+import dayjs from '@/lib/dayjs';
+
+defineProps({
+  data: {
+    type: Array,
+    default: () => [],
+  },
+});
+
+function getBlock(item) {
+  console.log(item.state.currentBlock);
+  if (!item.state.currentBlock) return {};
+
+  return tasks[item.state.currentBlock.name];
+}
+function formatDate(date, format) {
+  if (format === 'relative') return dayjs(date).fromNow();
+
+  return dayjs(date).format(format);
+}
+function openTab(tabId) {
+  browser.tabs.update(tabId, { active: true });
+}
+function stopWorkflow(item) {
+  sendMessage('workflow:stop', item, 'background');
+}
+</script>

+ 9 - 0
src/newtab/App.vue

@@ -14,6 +14,7 @@ import AppSidebar from '@/components/newtab/app/AppSidebar.vue';
 const store = useStore();
 const store = useStore();
 const retrieved = ref(false);
 const retrieved = ref(false);
 
 
+store.dispatch('retrieveWorkflowState');
 store
 store
   .dispatch('retrieve', ['workflows', 'logs'])
   .dispatch('retrieve', ['workflows', 'logs'])
   .then((res) => {
   .then((res) => {
@@ -31,6 +32,14 @@ function handleStorageChanged(change) {
       data: change.logs.newValue,
       data: change.logs.newValue,
     });
     });
   }
   }
+
+  if (change.workflowState) {
+    console.log(change.workflowState.newValue);
+    store.commit('updateState', {
+      key: 'workflowState',
+      value: change.workflowState.newValue,
+    });
+  }
 }
 }
 
 
 browser.storage.local.onChanged.addListener(handleStorageChanged);
 browser.storage.local.onChanged.addListener(handleStorageChanged);

+ 34 - 5
src/newtab/pages/workflows/[id].vue

@@ -22,12 +22,30 @@
         @delete="deleteWorkflow"
         @delete="deleteWorkflow"
       />
       />
     </div>
     </div>
-    <div class="flex-1 relative">
+    <div class="flex-1 relative overflow-auto">
       <div class="absolute px-3 rounded-lg bg-white z-10 left-0 m-4 top-0">
       <div class="absolute px-3 rounded-lg bg-white z-10 left-0 m-4 top-0">
-        <ui-tabs v-model="activeTab" class="border-none space-x-1">
+        <ui-tabs v-model="activeTab" class="border-none h-full space-x-1">
           <ui-tab value="editor">Editor</ui-tab>
           <ui-tab value="editor">Editor</ui-tab>
           <ui-tab value="logs">Logs</ui-tab>
           <ui-tab value="logs">Logs</ui-tab>
-          <ui-tab value="running">Running</ui-tab>
+          <ui-tab value="running" class="flex items-center">
+            Running
+            <span
+              v-if="workflowState.length > 0"
+              class="
+                ml-2
+                p-1
+                text-center
+                inline-block
+                text-xs
+                rounded-full
+                bg-black
+                text-white
+              "
+              style="min-width: 25px"
+            >
+              {{ workflowState.length }}
+            </span>
+          </ui-tab>
         </ui-tabs>
         </ui-tabs>
       </div>
       </div>
       <workflow-builder
       <workflow-builder
@@ -37,8 +55,12 @@
         @load="editor = $event"
         @load="editor = $event"
         @deleteBlock="deleteBlock"
         @deleteBlock="deleteBlock"
       />
       />
-      <div v-else-if="activeTab === 'logs'" class="container mt-24 px-4">
-        <logs-table :logs="logs" class="w-full" />
+      <div v-else class="container pb-4 mt-24 px-4">
+        <logs-table v-if="activeTab === 'logs'" :logs="logs" class="w-full" />
+        <workflow-running
+          v-else-if="activeTab === 'running'"
+          :data="workflowState"
+        />
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
@@ -65,6 +87,7 @@ import {
   onMounted,
   onMounted,
   onUnmounted,
   onUnmounted,
 } from 'vue';
 } from 'vue';
+import { useStore } from 'vuex';
 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
 import browser from 'webextension-polyfill';
 import browser from 'webextension-polyfill';
 import emitter from 'tiny-emitter/instance';
 import emitter from 'tiny-emitter/instance';
@@ -73,6 +96,7 @@ import { debounce } from '@/utils/helper';
 import { useDialog } from '@/composable/dialog';
 import { useDialog } from '@/composable/dialog';
 import Log from '@/models/log';
 import Log from '@/models/log';
 import Workflow from '@/models/workflow';
 import Workflow from '@/models/workflow';
+import WorkflowRunning from '@/components/newtab/workflow/WorkflowRunning.vue';
 import WorkflowBuilder from '@/components/newtab/workflow/WorkflowBuilder.vue';
 import WorkflowBuilder from '@/components/newtab/workflow/WorkflowBuilder.vue';
 import WorkflowSettings from '@/components/newtab/workflow/WorkflowSettings.vue';
 import WorkflowSettings from '@/components/newtab/workflow/WorkflowSettings.vue';
 import WorkflowEditBlock from '@/components/newtab/workflow/WorkflowEditBlock.vue';
 import WorkflowEditBlock from '@/components/newtab/workflow/WorkflowEditBlock.vue';
@@ -80,6 +104,7 @@ import WorkflowDetailsCard from '@/components/newtab/workflow/WorkflowDetailsCar
 import WorkflowDataColumns from '@/components/newtab/workflow/WorkflowDataColumns.vue';
 import WorkflowDataColumns from '@/components/newtab/workflow/WorkflowDataColumns.vue';
 import LogsTable from '@/components/newtab/LogsTable.vue';
 import LogsTable from '@/components/newtab/LogsTable.vue';
 
 
+const store = useStore();
 const route = useRoute();
 const route = useRoute();
 const router = useRouter();
 const router = useRouter();
 const dialog = useDialog();
 const dialog = useDialog();
@@ -95,6 +120,10 @@ const state = reactive({
   isDataChanged: false,
   isDataChanged: false,
   showDataColumnsModal: false,
   showDataColumnsModal: false,
 });
 });
+
+const workflowState = computed(() =>
+  store.getters.getWorkflowState(workflowId)
+);
 const workflow = computed(() => Workflow.find(workflowId) || {});
 const workflow = computed(() => Workflow.find(workflowId) || {});
 const logs = computed(() =>
 const logs = computed(() =>
   Log.query().where('workflowId', workflowId).orderBy('startedAt', 'desc').get()
   Log.query().where('workflowId', workflowId).orderBy('startedAt', 'desc').get()

+ 23 - 0
src/store/index.js

@@ -5,6 +5,18 @@ import * as models from '@/models';
 
 
 const store = createStore({
 const store = createStore({
   plugins: [vuexORM(models)],
   plugins: [vuexORM(models)],
+  state: () => ({
+    workflowState: [],
+  }),
+  mutations: {
+    updateState(state, { key, value }) {
+      state[key] = value;
+    },
+  },
+  getters: {
+    getWorkflowState: (state) => (id) =>
+      state.workflowState.filter(({ workflowId }) => workflowId === id),
+  },
   actions: {
   actions: {
     async retrieve({ dispatch, getters }, keys = 'workflows') {
     async retrieve({ dispatch, getters }, keys = 'workflows') {
       try {
       try {
@@ -26,6 +38,17 @@ const store = createStore({
         throw error;
         throw error;
       }
       }
     },
     },
+    async retrieveWorkflowState({ commit }) {
+      try {
+        const { workflowState } = await browser.storage.local.get(
+          'workflowState'
+        );
+
+        commit('updateState', { key: 'workflowState', value: workflowState });
+      } catch (error) {
+        console.error(error);
+      }
+    },
     saveToStorage({ getters }, key) {
     saveToStorage({ getters }, key) {
       return new Promise((resolve, reject) => {
       return new Promise((resolve, reject) => {
         if (!key) {
         if (!key) {