Browse Source

feat: add "on specific day" option in trigger

Ahmad Kholid 3 years ago
parent
commit
865a1737fa

+ 9 - 0
src/background/index.js

@@ -3,6 +3,7 @@ import { MessageListener } from '@/utils/message';
 import workflowState from './workflow-state';
 import workflowState from './workflow-state';
 import WorkflowEngine from './workflow-engine';
 import WorkflowEngine from './workflow-engine';
 import CollectionEngine from './collection-engine';
 import CollectionEngine from './collection-engine';
+import { registerSpecificDay } from '../utils/workflow-trigger';
 
 
 function getWorkflow(workflowId) {
 function getWorkflow(workflowId) {
   return new Promise((resolve) => {
   return new Promise((resolve) => {
@@ -71,6 +72,14 @@ browser.alarms.onAlarm.addListener(({ name }) => {
     if (!workflow) return;
     if (!workflow) return;
 
 
     executeWorkflow(workflow);
     executeWorkflow(workflow);
+
+    const triggerBlock = Object.values(
+      JSON.parse(workflow.drawflow).drawflow.Home.data
+    ).find((block) => block.name === 'trigger');
+
+    if (triggerBlock?.data.type === 'specific-day') {
+      registerSpecificDay(workflow.id, triggerBlock.data);
+    }
   });
   });
 });
 });
 
 

+ 1 - 1
src/background/workflow-engine/blocks-handler.js

@@ -68,7 +68,7 @@ export async function trigger(block) {
   const nextBlockId = getBlockConnection(block);
   const nextBlockId = getBlockConnection(block);
   try {
   try {
     if (block.data.type === 'visit-web' && this.tabId) {
     if (block.data.type === 'visit-web' && this.tabId) {
-      this.frames = executeContentScript(this.tabId, 'trigger');
+      this.frames = await executeContentScript(this.tabId, 'trigger');
     }
     }
 
 
     return { nextBlockId, data: '' };
     return { nextBlockId, data: '' };

+ 1 - 1
src/components/newtab/workflow/WorkflowBuilder.vue

@@ -192,7 +192,7 @@ export default {
           50,
           50,
           300,
           300,
           'trigger',
           'trigger',
-          { type: 'manual' },
+          tasks.trigger.data,
           'BlockBasic',
           'BlockBasic',
           'vue'
           'vue'
         );
         );

+ 145 - 98
src/components/newtab/workflow/edit/EditTrigger.vue

@@ -1,106 +1,135 @@
 <template>
 <template>
-  <ui-textarea
-    :model-value="data.description"
-    autoresize
-    placeholder="Description"
-    class="w-full mb-2"
-    @change="updateData({ description: $event })"
-  />
-  <ui-select
-    :model-value="data.type || 'manual'"
-    placeholder="Trigger workflow"
-    class="w-full"
-    @change="handleSelectChange"
-  >
-    <option v-for="trigger in triggers" :key="trigger.id" :value="trigger.id">
-      {{ trigger.name }}
-    </option>
-  </ui-select>
-  <transition-expand mode="out-in">
-    <div v-if="data.type === 'interval'" class="flex items-center mt-1">
-      <ui-input
-        :model-value="data.interval"
-        type="number"
-        class="w-full mr-2"
-        label="Interval (minutes)"
-        placeholder="1-120"
-        min="1"
-        max="120"
-        @change="
-          updateIntervalInput($event, { key: 'interval', min: 1, max: 120 })
-        "
-      />
-      <ui-input
-        :model-value="data.delay"
-        type="number"
-        class="w-full"
-        label="Delay (minutes)"
-        min="0"
-        max="20"
-        placeholder="0-20"
-        @change="updateIntervalInput($event, { key: 'delay', min: 0, max: 20 })"
-      />
-    </div>
-    <div v-else-if="data.type === 'date'" class="mt-2">
-      <ui-input
-        :model-value="data.date"
-        :max="maxDate"
-        :min="minDate"
-        class="w-full"
-        type="date"
-        placeholder="Date"
-        @change="updateDate({ date: $event })"
-      />
-      <ui-input
-        :model-value="data.time"
-        type="time"
-        class="w-full mt-2"
-        placeholder="Time"
-        @change="updateData({ time: $event || '00:00' })"
-      />
-    </div>
-    <div v-else-if="data.type === 'visit-web'" class="mt-2">
-      <ui-input
-        :model-value="data.url"
-        placeholder="URL or Regex"
-        class="w-full"
-        @change="updateData({ url: $event })"
-      />
-      <ui-checkbox
-        :model-value="data.isUrlRegex"
-        class="mt-1"
-        @change="updateData({ isUrlRegex: $event })"
-      >
-        Use regex
-      </ui-checkbox>
-    </div>
-    <div v-else-if="data.type === 'keyboard-shortcut'" class="mt-2">
-      <div class="flex items-center mb-2">
+  <div class="trigger">
+    <ui-textarea
+      :model-value="data.description"
+      autoresize
+      placeholder="Description"
+      class="w-full mb-2"
+      @change="updateData({ description: $event })"
+    />
+    <ui-select
+      :model-value="data.type || 'manual'"
+      placeholder="Trigger workflow"
+      class="w-full"
+      @change="handleSelectChange"
+    >
+      <option v-for="trigger in triggers" :key="trigger.id" :value="trigger.id">
+        {{ trigger.name }}
+      </option>
+    </ui-select>
+    <transition-expand mode="out-in">
+      <div v-if="data.type === 'interval'" class="flex items-center mt-1">
         <ui-input
         <ui-input
-          :model-value="recordKeys.keys"
-          readonly
-          class="flex-1 mr-2"
-          placeholder="Shortcut"
+          :model-value="data.interval"
+          type="number"
+          class="w-full mr-2"
+          label="Interval (minutes)"
+          placeholder="1-120"
+          min="1"
+          max="120"
+          @change="
+            updateIntervalInput($event, { key: 'interval', min: 1, max: 120 })
+          "
         />
         />
-        <ui-button v-tooltip="'Record shortcut'" icon @click="toggleRecordKeys">
-          <v-remixicon
-            :name="recordKeys.isRecording ? 'riStopLine' : 'riRecordCircleLine'"
+        <ui-input
+          :model-value="data.delay"
+          type="number"
+          class="w-full"
+          label="Delay (minutes)"
+          min="0"
+          max="20"
+          placeholder="0-20"
+          @change="
+            updateIntervalInput($event, { key: 'delay', min: 0, max: 20 })
+          "
+        />
+      </div>
+      <div v-else-if="data.type === 'date'" class="mt-2">
+        <ui-input
+          :model-value="data.date"
+          :max="maxDate"
+          :min="minDate"
+          class="w-full"
+          type="date"
+          placeholder="Date"
+          @change="updateDate({ date: $event })"
+        />
+        <ui-input
+          :model-value="data.time"
+          type="time"
+          class="w-full mt-2"
+          placeholder="Time"
+          @change="updateData({ time: $event || '00:00' })"
+        />
+      </div>
+      <div v-else-if="data.type === 'specific-day'" class="mt-2">
+        <ui-input
+          :model-value="data.time"
+          type="time"
+          class="w-full my-2"
+          placeholder="Time"
+          @change="updateData({ time: $event || '00:00' })"
+        />
+        <div class="grid gap-2 grid-cols-2">
+          <ui-checkbox
+            v-for="day in days"
+            :key="day.id"
+            :model-value="data.days.includes(day.id)"
+            @change="onDayChange($event, day.id)"
+          >
+            {{ day.name }}
+          </ui-checkbox>
+        </div>
+      </div>
+      <div v-else-if="data.type === 'visit-web'" class="mt-2">
+        <ui-input
+          :model-value="data.url"
+          placeholder="URL or Regex"
+          class="w-full"
+          @change="updateData({ url: $event })"
+        />
+        <ui-checkbox
+          :model-value="data.isUrlRegex"
+          class="mt-1"
+          @change="updateData({ isUrlRegex: $event })"
+        >
+          Use regex
+        </ui-checkbox>
+      </div>
+      <div v-else-if="data.type === 'keyboard-shortcut'" class="mt-2">
+        <div class="flex items-center mb-2">
+          <ui-input
+            :model-value="recordKeys.keys"
+            readonly
+            class="flex-1 mr-2"
+            placeholder="Shortcut"
           />
           />
-        </ui-button>
+          <ui-button
+            v-tooltip="'Record shortcut'"
+            icon
+            @click="toggleRecordKeys"
+          >
+            <v-remixicon
+              :name="
+                recordKeys.isRecording ? 'riStopLine' : 'riRecordCircleLine'
+              "
+            />
+          </ui-button>
+        </div>
+        <ui-checkbox
+          :model-value="data.activeInInput"
+          class="mb-1"
+          title="Execute shortcut even when you're in an input element"
+          @change="updateData({ activeInInput: $event })"
+        >
+          Active while in input
+        </ui-checkbox>
+        <p class="mt-4 leading-tight text-gray-600 dark:text-gray-200">
+          Note: keyboard shortcut only working when you're on a webpage
+        </p>
       </div>
       </div>
-      <ui-checkbox
-        :model-value="data.activeInInput"
-        class="mb-1"
-        title="Execute shortcut even in an input element"
-        @change="updateData({ activeInInput: $event })"
-      >
-        Active while in input
-      </ui-checkbox>
-      <p class="mt-4 leading-tight text-gray-600 dark:text-gray-200">
-        Note: keyboard shortcut only working when you're on a webpage
-      </p>
-    </div>
-  </transition-expand>
+    </transition-expand>
+  </div>
 </template>
 </template>
 <script setup>
 <script setup>
 import { shallowReactive, onUnmounted } from 'vue';
 import { shallowReactive, onUnmounted } from 'vue';
@@ -118,9 +147,19 @@ const triggers = [
   { id: 'manual', name: 'Manually' },
   { id: 'manual', name: 'Manually' },
   { id: 'interval', name: 'Interval' },
   { id: 'interval', name: 'Interval' },
   { id: 'date', name: 'On specific date' },
   { id: 'date', name: 'On specific date' },
+  { id: 'specific-day', name: 'On specific day' },
   { id: 'visit-web', name: 'When visit a website' },
   { id: 'visit-web', name: 'When visit a website' },
   { id: 'keyboard-shortcut', name: 'Keyboard shortcut' },
   { id: 'keyboard-shortcut', name: 'Keyboard shortcut' },
 ];
 ];
+const days = [
+  { id: 0, name: 'Sunday' },
+  { id: 1, name: 'Monday' },
+  { id: 2, name: 'Tuesday' },
+  { id: 3, name: 'Wednesday' },
+  { id: 4, name: 'Thursday' },
+  { id: 5, name: 'Friday' },
+  { id: 6, name: 'Saturday' },
+];
 const maxDate = dayjs().add(30, 'day').format('YYYY-MM-DD');
 const maxDate = dayjs().add(30, 'day').format('YYYY-MM-DD');
 const minDate = dayjs().format('YYYY-MM-DD');
 const minDate = dayjs().format('YYYY-MM-DD');
 const allowedKeys = {
 const allowedKeys = {
@@ -143,6 +182,14 @@ const recordKeys = shallowReactive({
 function updateData(value) {
 function updateData(value) {
   emit('update:data', { ...props.data, ...value });
   emit('update:data', { ...props.data, ...value });
 }
 }
+function onDayChange(value, id) {
+  const dataDays = [...props.data.days];
+
+  if (value) dataDays.push(id);
+  else dataDays.splice(dataDays.indexOf(id), 1);
+
+  updateData({ days: dataDays.sort() });
+}
 function handleKeydownEvent(event) {
 function handleKeydownEvent(event) {
   event.preventDefault();
   event.preventDefault();
   event.stopPropagation();
   event.stopPropagation();

+ 0 - 1
src/newtab/App.vue

@@ -6,7 +6,6 @@
   <ui-dialog />
   <ui-dialog />
 </template>
 </template>
 <script setup>
 <script setup>
-/* to-do add documentation of the extension */
 import { ref } from 'vue';
 import { ref } from 'vue';
 import { useStore } from 'vuex';
 import { useStore } from 'vuex';
 import browser from 'webextension-polyfill';
 import browser from 'webextension-polyfill';

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

@@ -123,7 +123,6 @@ import {
 } from 'vue';
 } from 'vue';
 import { useStore } from 'vuex';
 import { useStore } from 'vuex';
 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
-import browser from 'webextension-polyfill';
 import emitter from 'tiny-emitter/instance';
 import emitter from 'tiny-emitter/instance';
 import { sendMessage } from '@/utils/message';
 import { sendMessage } from '@/utils/message';
 import { debounce } from '@/utils/helper';
 import { debounce } from '@/utils/helper';
@@ -131,6 +130,7 @@ import { useDialog } from '@/composable/dialog';
 import { exportWorkflow } from '@/utils/workflow-data';
 import { exportWorkflow } from '@/utils/workflow-data';
 import Log from '@/models/log';
 import Log from '@/models/log';
 import Workflow from '@/models/workflow';
 import Workflow from '@/models/workflow';
+import workflowTrigger from '@/utils/workflow-trigger';
 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';
@@ -197,85 +197,6 @@ function updateWorkflow(data) {
     data,
     data,
   });
   });
 }
 }
-function convertToTimestamp(date, hourMinutes) {
-  let timestamp = Date.now() + 60000;
-  if (date) {
-    const dateObj = new Date(date);
-    if (hourMinutes) {
-      const arr = hourMinutes.split(':');
-      dateObj.setHours(arr[0]);
-      dateObj.setMinutes(arr[1]);
-    }
-
-    timestamp = dateObj.getTime();
-  }
-
-  return timestamp;
-}
-async function handleWorkflowTrigger({ data }) {
-  try {
-    const workflowAlarm = await browser.alarms.get(workflowId);
-    const { visitWebTriggers, shortcuts } = await browser.storage.local.get([
-      'visitWebTriggers',
-      'shortcuts',
-    ]);
-    let visitWebTriggerIndex = visitWebTriggers.findIndex(
-      (item) => item.id === workflowId
-    );
-    const keyboardShortcuts = Array.isArray(shortcuts) ? {} : shortcuts || {};
-    delete keyboardShortcuts[workflowId];
-
-    if (workflowAlarm) await browser.alarms.clear(workflowId);
-    if (visitWebTriggerIndex !== -1) {
-      visitWebTriggers.splice(visitWebTriggerIndex, 1);
-
-      visitWebTriggerIndex = -1;
-    }
-
-    await browser.storage.local.set({
-      visitWebTriggers,
-      shortcuts: keyboardShortcuts,
-    });
-
-    if (['date', 'interval'].includes(data.type)) {
-      let alarmInfo;
-
-      if (data.type === 'date') {
-        alarmInfo = {
-          when: convertToTimestamp(data.date, data.time),
-        };
-      } else {
-        alarmInfo = {
-          periodInMinutes: data.interval,
-        };
-
-        if (data.delay > 0) alarmInfo.delayInMinutes = data.delay;
-      }
-
-      if (alarmInfo) await browser.alarms.create(workflowId, alarmInfo);
-    } else if (data.type === 'visit-web' && data.url.trim() !== '') {
-      const payload = {
-        id: workflowId,
-        url: data.url,
-        isRegex: data.isUrlRegex,
-      };
-
-      if (visitWebTriggerIndex === -1) {
-        visitWebTriggers.unshift(payload);
-      } else {
-        visitWebTriggers[visitWebTriggerIndex] = payload;
-      }
-
-      await browser.storage.local.set({ visitWebTriggers });
-    } else if (data.type === 'keyboard-shortcut') {
-      keyboardShortcuts[workflowId] = data.shortcut;
-
-      await browser.storage.local.set({ shortcuts: keyboardShortcuts });
-    }
-  } catch (error) {
-    console.error(error);
-  }
-}
 function saveWorkflow() {
 function saveWorkflow() {
   const data = editor.value.export();
   const data = editor.value.export();
 
 
@@ -283,7 +204,10 @@ function saveWorkflow() {
     const [triggerBlockId] = editor.value.getNodesFromName('trigger');
     const [triggerBlockId] = editor.value.getNodesFromName('trigger');
 
 
     if (triggerBlockId) {
     if (triggerBlockId) {
-      handleWorkflowTrigger(editor.value.getNodeFromId(triggerBlockId));
+      workflowTrigger.register(
+        workflowId,
+        editor.value.getNodeFromId(triggerBlockId)
+      );
     }
     }
 
 
     state.isDataChanged = false;
     state.isDataChanged = false;

+ 1 - 0
src/utils/shared.js

@@ -23,6 +23,7 @@ export const tasks = {
       shortcut: '',
       shortcut: '',
       activeInInput: false,
       activeInInput: false,
       isUrlRegex: false,
       isUrlRegex: false,
+      days: [],
     },
     },
   },
   },
   'active-tab': {
   'active-tab': {

+ 133 - 0
src/utils/workflow-trigger.js

@@ -0,0 +1,133 @@
+import browser from 'webextension-polyfill';
+import dayjs from 'dayjs';
+
+export async function cleanWorkflowTriggers(workflowId) {
+  try {
+    await browser.alarms.clear(workflowId);
+
+    const { visitWebTriggers, shortcuts } = await browser.storage.local.get([
+      'visitWebTriggers',
+      'shortcuts',
+    ]);
+
+    const keyboardShortcuts = Array.isArray(shortcuts) ? {} : shortcuts || {};
+    delete keyboardShortcuts[workflowId];
+
+    const visitWebTriggerIndex = visitWebTriggers.findIndex(
+      (item) => item.id === workflowId
+    );
+    if (visitWebTriggerIndex !== -1) {
+      visitWebTriggers.splice(visitWebTriggerIndex, 1);
+    }
+
+    await browser.storage.local.set({
+      visitWebTriggers,
+      shortcuts: keyboardShortcuts,
+    });
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+export function registerSpecificDay(workflowId, data) {
+  if (data.days.length === 0) return null;
+
+  const [hour, minute] = data.time.split(':');
+  const dates = data.days.map((id) =>
+    dayjs().day(id).hour(hour).minute(minute)
+  );
+  const findDate =
+    dates.find((date) => date.valueOf() > Date.now()) || dates[0].add(7, 'day');
+
+  console.log(finDate.valueOf(), new Date(finDate.valueOf()));
+
+  return browser.alarms.create(workflowId, {
+    when: findDate.valueOf(),
+  });
+}
+
+export function registerInterval(workflowId, data) {
+  const alarmInfo = {
+    periodInMinutes: data.interval,
+  };
+
+  if (data.delay > 0) alarmInfo.delayInMinutes = data.delay;
+
+  return browser.alarms.create(workflowId, alarmInfo);
+}
+
+export function registerSpecificDate(workflowId, data) {
+  let date = Date.now() + 60000;
+
+  if (data.date) {
+    const [hour, minute] = data.time.split(':');
+    date = dayjs(data.data).hour(hour).minute(minute).valueOf();
+  }
+
+  return browser.alarms.create(workflowId, {
+    when: date,
+  });
+}
+
+export async function registerVisitWeb(workflowId, data) {
+  try {
+    if (data.url.trim() === '') return;
+
+    const visitWebTriggers =
+      (await browser.storage.local.get('visitWebTriggers'))?.visitWebTriggers ||
+      [];
+
+    const index = visitWebTriggers.findIndex((item) => item.id === workflowId);
+    const payload = {
+      id: workflowId,
+      url: data.url,
+      isRegex: data.isUrlRegex,
+    };
+
+    if (index === -1) {
+      visitWebTriggers.unshift(payload);
+    } else {
+      visitWebTriggers[index] = payload;
+    }
+
+    await browser.storage.local.set({ visitWebTriggers });
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+export async function registerKeyboardShortcut(workflowId, data) {
+  try {
+    const { shortcuts } = await browser.storage.local.get('shortcuts');
+    const keyboardShortcuts = Array.isArray(shortcuts) ? {} : shortcuts || {};
+
+    keyboardShortcuts[workflowId] = data.shortcut;
+
+    await browser.storage.local.set({ shortcuts: keyboardShortcuts });
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+export async function registerWorkflowTrigger(workflowId, { data }) {
+  try {
+    await cleanWorkflowTriggers(workflowId);
+
+    const triggersHandler = {
+      date: registerSpecificDate,
+      interval: registerInterval,
+      'visit-web': registerVisitWeb,
+      'specific-day': registerSpecificDay,
+      'keyboard-shortcut': registerKeyboardShortcut,
+    };
+
+    await triggersHandler[data.type](workflowId, data);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+export default {
+  cleanUp: cleanWorkflowTriggers,
+  register: registerWorkflowTrigger,
+};