Browse Source

feat: team workflows in popup

Ahmad Kholid 2 years ago
parent
commit
4dac9833f9

+ 81 - 0
src/components/popup/home/HomeTeamWorkflows.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="px-5 pb-5 space-y-2">
+    <ui-card
+      v-for="workflow in workflows"
+      :key="workflow.id"
+      class="w-full flex items-center space-x-2 hover:ring-2 hover:ring-gray-900"
+    >
+      <div
+        class="flex-1 text-overflow cursor-pointer mr-4"
+        @click="openWorkflowPage(workflow)"
+      >
+        <p class="leading-tight text-overflow">{{ workflow.name }}</p>
+        <div class="text-gray-500 flex items-center">
+          <span>{{ dayjs(workflow.createdAt).fromNow() }}</span>
+          <span
+            :title="`Team name: ${workflow.teamName}`"
+            class="inline-block text-overflow px-2 text-sm ml-2 text-gray-600 py-1 rounded-md bg-box-transparent w-full"
+            style="max-width: 120px"
+          >
+            {{ workflow.teamName }}
+          </span>
+        </div>
+      </div>
+      <p v-if="workflow.isDisabled" class="text-sm text-gray-600">Disabled</p>
+      <button v-else title="Execute" @click="executeWorkflow(workflow)">
+        <v-remixicon name="riPlayLine" />
+      </button>
+    </ui-card>
+  </div>
+</template>
+<script setup>
+import { computed, onMounted, shallowRef } from 'vue';
+import { useUserStore } from '@/stores/user';
+import { sendMessage } from '@/utils/message';
+import { useTeamWorkflowStore } from '@/stores/teamWorkflow';
+import dayjs from '@/lib/dayjs';
+
+const props = defineProps({
+  search: {
+    type: String,
+    default: '',
+  },
+});
+
+const userStore = useUserStore();
+const teamWorkflowStore = useTeamWorkflowStore();
+
+const teamWorkflows = shallowRef([]);
+
+const workflows = computed(() =>
+  teamWorkflows.value.filter((workflow) =>
+    workflow.name.toLocaleLowerCase().includes(props.search.toLocaleLowerCase())
+  )
+);
+
+function openWorkflowPage({ teamId, id }) {
+  const url = `/teams/${teamId}/workflows/${id}`;
+  sendMessage('open:dashboard', url, 'background');
+}
+function executeWorkflow(workflow) {
+  sendMessage('workflow:execute', workflow, 'background');
+}
+
+onMounted(() => {
+  if (!userStore.user?.teams) return;
+
+  teamWorkflows.value = userStore.user.teams.reduce((acc, team) => {
+    const currentWorkflows = teamWorkflowStore
+      .getByTeam(team.id)
+      .map((workflow) => {
+        workflow.teamId = team.id;
+        workflow.teamName = team.name;
+
+        return workflow;
+      });
+    acc.push(...currentWorkflows);
+
+    return acc;
+  }, []);
+});
+</script>

+ 1 - 1
src/newtab/pages/Workflows.vue

@@ -112,7 +112,7 @@
                 </span>
               </ui-list-item>
               <ui-list-item
-                v-if="hostedWorkflows.length > 0"
+                v-if="hostedWorkflows?.length > 0"
                 :active="state.activeTab === 'host'"
                 color="bg-box-transparent font-semibold"
                 tag="button"

+ 43 - 8
src/popup/pages/Home.vue

@@ -1,10 +1,10 @@
 <template>
   <div
-    :class="[hostedWorkflowStore.toArray.length === 0 ? 'h-48' : 'h-56']"
+    :class="[!showTab ? 'h-48' : 'h-56']"
     class="bg-accent rounded-b-2xl absolute top-0 left-0 w-full"
   ></div>
   <div
-    :class="[hostedWorkflowStore.toArray.length === 0 ? 'mb-6' : 'mb-2']"
+    :class="[!showTab ? 'mb-6' : 'mb-2']"
     class="dark placeholder-black relative z-10 text-white px-5 pt-8"
   >
     <div class="flex items-center mb-4">
@@ -41,22 +41,33 @@
       <ui-input
         v-model="state.query"
         :placeholder="`${t('common.search')}...`"
+        autocomplete="off"
         prepend-icon="riSearch2Line"
         class="w-full search-input"
       />
     </div>
     <ui-tabs
-      v-if="hostedWorkflowStore.toArray.length > 0"
+      v-if="showTab"
       v-model="state.activeTab"
       fill
       class="mt-1"
+      @change="onTabChange"
     >
-      <ui-tab v-for="type in workflowTypes" :key="type" :value="type">
-        {{ t(`home.workflow.type.${type}`) }}
+      <ui-tab value="local">
+        {{ t(`home.workflow.type.local`) }}
       </ui-tab>
+      <ui-tab v-if="hostedWorkflowStore.toArray.length > 0" value="host">
+        {{ t(`home.workflow.type.host`) }}
+      </ui-tab>
+      <ui-tab v-if="userStore.user?.teams" value="team"> Teams </ui-tab>
     </ui-tabs>
   </div>
-  <div class="px-5 pb-5 space-y-2">
+  <home-team-workflows
+    v-if="state.retrieved"
+    v-show="state.activeTab === 'team'"
+    :search="state.query"
+  />
+  <div v-if="state.activeTab !== 'team'" class="px-5 pb-5 space-y-2">
     <ui-card v-if="workflowStore.getWorkflows.length === 0" class="text-center">
       <img src="@/assets/svg/alien.svg" />
       <p class="font-semibold">{{ t('message.empty') }}</p>
@@ -109,15 +120,17 @@
 import { computed, onMounted, shallowReactive } from 'vue';
 import { useI18n } from 'vue-i18n';
 import browser from 'webextension-polyfill';
+import { useUserStore } from '@/stores/user';
 import { useDialog } from '@/composable/dialog';
-import { useGroupTooltip } from '@/composable/groupTooltip';
 import { sendMessage } from '@/utils/message';
 import { useWorkflowStore } from '@/stores/workflow';
+import { useGroupTooltip } from '@/composable/groupTooltip';
+import { useTeamWorkflowStore } from '@/stores/teamWorkflow';
 import { useHostedWorkflowStore } from '@/stores/hostedWorkflow';
 import HomeWorkflowCard from '@/components/popup/home/HomeWorkflowCard.vue';
+import HomeTeamWorkflows from '@/components/popup/home/HomeTeamWorkflows.vue';
 import HomeStartRecording from '@/components/popup/home/HomeStartRecording.vue';
 
-const workflowTypes = ['local', 'host'];
 const recordingCardHeight = {
   new: 255,
   existing: 480,
@@ -125,14 +138,18 @@ const recordingCardHeight = {
 
 const { t } = useI18n();
 const dialog = useDialog();
+const userStore = useUserStore();
 const workflowStore = useWorkflowStore();
+const teamWorkflowStore = useTeamWorkflowStore();
 const hostedWorkflowStore = useHostedWorkflowStore();
 
 useGroupTooltip();
 
 const state = shallowReactive({
   query: '',
+  teams: [],
   cardHeight: 255,
+  retrieved: false,
   haveAccess: true,
   activeTab: 'local',
   newRecordingModal: false,
@@ -157,6 +174,9 @@ const localWorkflows = computed(() => {
 const workflows = computed(() =>
   state.activeTab === 'local' ? localWorkflows.value : hostedWorkflows.value
 );
+const showTab = computed(
+  () => hostedWorkflowStore.toArray.length > 0 || userStore.user?.teams
+);
 
 function executeWorkflow(workflow) {
   sendMessage('workflow:execute', workflow, 'background');
@@ -268,10 +288,25 @@ function openWorkflowPage({ id, hostId }) {
 
   openDashboard(url);
 }
+function onTabChange(value) {
+  localStorage.setItem('popup-tab', value);
+}
 
 onMounted(async () => {
   const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
   state.haveAccess = /^(https?)/.test(tab.url);
+
+  await userStore.loadUser({ storage: localStorage, ttl: 1000 * 60 * 5 });
+  await teamWorkflowStore.loadData();
+
+  let activeTab = localStorage.getItem('popup-tab') || 'local';
+
+  if (activeTab === 'team' && !userStore.user?.teams) activeTab = 'local';
+  else if (activeTab === 'host' && hostedWorkflowStore.toArray.length < 0)
+    activeTab = 'local';
+
+  state.retrieved = true;
+  state.activeTab = activeTab;
 });
 </script>
 <style>

+ 18 - 5
src/stores/user.js

@@ -1,6 +1,6 @@
 import { defineStore } from 'pinia';
 import browser from 'webextension-polyfill';
-import { fetchApi } from '@/utils/api';
+import { fetchApi, cacheApi } from '@/utils/api';
 
 export const useUserStore = defineStore('user', {
   state: () => ({
@@ -23,12 +23,25 @@ export const useUserStore = defineStore('user', {
       },
   },
   actions: {
-    async loadUser() {
+    async loadUser(options = false) {
       try {
-        const response = await fetchApi('/me');
-        const user = await response.json();
+        const user = await cacheApi(
+          'user-profile',
+          async () => {
+            try {
+              const response = await fetchApi('/me');
+              const result = await response.json();
 
-        if (!response.ok) throw new Error(response.message);
+              if (!response.ok) throw new Error(response.message);
+
+              return result;
+            } catch (error) {
+              console.error(error);
+              return null;
+            }
+          },
+          options
+        );
 
         const username = localStorage.getItem('username');
         if (!user || username !== user.username) {

+ 18 - 8
src/utils/api.js

@@ -1,6 +1,6 @@
 import secrets from 'secrets';
 import browser from 'webextension-polyfill';
-import { parseJSON } from './helper';
+import { parseJSON, isObject } from './helper';
 
 function queryBuilder(obj) {
   let str = '';
@@ -62,14 +62,24 @@ export const googleSheets = {
 };
 
 export async function cacheApi(key, callback, useCache = true) {
-  const tenMinutes = 1000 * 10;
-  const tenMinutesAgo = Date.now() - tenMinutes;
+  const isBoolOpts = typeof useCache === 'boolean';
+  const options = {
+    ttl: 10000 * 10,
+    storage: sessionStorage,
+    useCache: isBoolOpts ? useCache : true,
+  };
+  if (!isBoolOpts && isObject(useCache)) {
+    Object.assign(options, useCache);
+  }
+
+  const timeToLive = options.ttl;
+  const currentTime = Date.now() - timeToLive;
 
   const timerKey = `cache-time:${key}`;
-  const cacheResult = parseJSON(sessionStorage.getItem(key), null);
-  const cacheTime = +sessionStorage.getItem(timerKey) || Date.now();
+  const cacheResult = parseJSON(options.storage.getItem(key), null);
+  const cacheTime = +options.storage.getItem(timerKey) || Date.now();
 
-  if (useCache && cacheResult && tenMinutesAgo < cacheTime) {
+  if (options.useCache && cacheResult && currentTime < cacheTime) {
     return cacheResult;
   }
 
@@ -80,8 +90,8 @@ export async function cacheApi(key, callback, useCache = true) {
     cacheData = result?.cacheData;
   }
 
-  sessionStorage.setItem(timerKey, Date.now());
-  sessionStorage.setItem(key, JSON.stringify(cacheData));
+  options.storage.setItem(timerKey, Date.now());
+  options.storage.setItem(key, JSON.stringify(cacheData));
 
   return result;
 }