Browse Source

feat: add pinned blocks

Ahmad Kholid 3 years ago
parent
commit
aa361cf42a

+ 87 - 0
src/components/newtab/workflow/WorkflowBlockList.vue

@@ -0,0 +1,87 @@
+<template>
+  <ui-expand
+    hide-header-icon
+    header-class="flex items-center py-2 focus:ring-0 w-full text-left text-gray-600 dark:text-gray-200"
+  >
+    <template #header="{ show }">
+      <span :class="category.color" class="h-3 w-3 rounded-full"></span>
+      <p class="capitalize flex-1 ml-2">
+        {{ category.name }}
+      </p>
+      <v-remixicon :name="show ? 'riSubtractLine' : 'riAddLine'" size="20" />
+    </template>
+    <div class="grid grid-cols-2 gap-2 mb-4">
+      <div
+        v-for="block in blocks"
+        :key="block.id"
+        :title="getBlockTitle(block)"
+        draggable="true"
+        class="transform select-none cursor-move relative p-4 rounded-lg bg-input transition group"
+        @dragstart="$event.dataTransfer.setData('block', JSON.stringify(block))"
+      >
+        <div
+          class="flex items-center absolute right-2 invisible group-hover:visible top-2 text-gray-600 dark:text-gray-300"
+        >
+          <a
+            :href="`https://docs.automa.site/blocks/${block.id}.html`"
+            :title="t('common.docs')"
+            target="_blank"
+            rel="noopener"
+          >
+            <v-remixicon name="riInformationLine" size="18" />
+          </a>
+          <span
+            :title="`${pinned.includes(block.id) ? 'Unpin' : 'Pin'} block`"
+            class="cursor-pointer ml-1"
+            @click="$emit('pin', block)"
+          >
+            <v-remixicon
+              size="18"
+              :name="
+                pinned.includes(block.id) ? 'riPushpin2Fill' : 'riPushpin2Line'
+              "
+            />
+          </span>
+        </div>
+        <v-remixicon :name="block.icon" size="24" class="mb-2" />
+        <p class="leading-tight text-overflow capitalize">
+          {{ block.name }}
+        </p>
+      </div>
+    </div>
+  </ui-expand>
+</template>
+<script setup>
+import { useI18n } from 'vue-i18n';
+
+defineProps({
+  category: {
+    type: Object,
+    default: () => ({}),
+  },
+  blocks: {
+    type: Array,
+    default: () => [],
+  },
+  pinned: {
+    type: Array,
+    default: () => [],
+  },
+});
+defineEmits(['pin']);
+
+const { t } = useI18n();
+
+function getBlockTitle({ description, id }) {
+  const blockPath = `workflow.blocks.${id}`;
+  let blockDescription = t(
+    `${blockPath}.${description ? 'description' : 'name'}`
+  );
+
+  if (description) {
+    blockDescription = `[${t(`${blockPath}.name`)}]\n${blockDescription}`;
+  }
+
+  return blockDescription;
+}
+</script>

+ 38 - 80
src/components/newtab/workflow/WorkflowDetailsCard.vue

@@ -59,76 +59,32 @@
     class="px-4 mt-4 mb-2"
   />
   <div class="scroll bg-scroll px-4 flex-1 relative overflow-auto">
-    <ui-expand
+    <workflow-block-list
+      v-if="pinnedBlocksList.length > 0"
+      :model-value="true"
+      :blocks="pinnedBlocksList"
+      :category="pinnedCategory"
+      :pinned="pinnedBlocks"
+      @pin="pinBlock"
+    />
+    <workflow-block-list
       v-for="(items, catId) in blocks"
       :key="catId"
-      v-model="expandList[catId]"
-      hide-header-icon
-      header-class="flex items-center py-2 focus:ring-0 w-full text-left text-gray-600 dark:text-gray-200"
-    >
-      <template #header="{ show }">
-        <span
-          :class="categories[catId].color"
-          class="h-3 w-3 rounded-full"
-        ></span>
-        <p class="capitalize flex-1 ml-2">
-          {{ categories[catId].name }}
-        </p>
-        <v-remixicon :name="show ? 'riSubtractLine' : 'riAddLine'" size="20" />
-      </template>
-      <div class="grid grid-cols-2 gap-2 mb-4">
-        <div
-          v-for="block in items"
-          :key="block.id"
-          :title="getBlockTitle(block)"
-          draggable="true"
-          class="transform select-none cursor-move relative p-4 rounded-lg bg-input transition group"
-          @dragstart="
-            $event.dataTransfer.setData('block', JSON.stringify(block))
-          "
-        >
-          <div
-            class="flex items-center absolute right-2 invisible group-hover:visible top-2"
-          >
-            <a
-              :href="`https://docs.automa.site/blocks/${block.id}.html`"
-              :title="t('common.docs')"
-              target="_blank"
-              rel="noopener"
-              class="text-gray-600 dark:text-gray-300"
-            >
-              <v-remixicon name="riInformationLine" size="18" />
-            </a>
-            <span
-              title="Pin block"
-              class="cursor-pointer ml-1"
-              @click="pinBlock(block)"
-            >
-              <v-remixicon
-                size="18"
-                :name="
-                  pinnedBlocks.includes(block.id)
-                    ? 'riPushpin2Fill'
-                    : 'riPushpin2Line'
-                "
-              />
-            </span>
-          </div>
-          <v-remixicon :name="block.icon" size="24" class="mb-2" />
-          <p class="leading-tight text-overflow capitalize">
-            {{ block.name }}
-          </p>
-        </div>
-      </div>
-    </ui-expand>
+      :model-value="true"
+      :blocks="items"
+      :category="categories[catId]"
+      :pinned="pinnedBlocks"
+      @pin="pinBlock"
+    />
   </div>
 </template>
 <script setup>
-import { computed, ref, onMounted } from 'vue';
+import { computed, ref, onMounted, watch, toRaw } from 'vue';
 import { useI18n } from 'vue-i18n';
 import browser from 'webextension-polyfill';
 import { useShortcut } from '@/composable/shortcut';
 import { tasks, categories } from '@/utils/shared';
+import WorkflowBlockList from './WorkflowBlockList.vue';
 
 defineProps({
   workflow: {
@@ -149,6 +105,10 @@ const shortcut = useShortcut('action:search', () => {
   searchInput?.focus();
 });
 
+const pinnedCategory = {
+  name: 'Pinned blocks',
+  color: 'bg-accent',
+};
 const icons = [
   'riGlobalLine',
   'riFileTextLine',
@@ -169,30 +129,30 @@ const blocksArr = Object.entries(tasks).map(([key, block]) => ({
   id: key,
   name: t(`workflow.blocks.${key}.name`),
 }));
-const categoriesExpand = Object.keys(categories).reduce((acc, key) => {
-  acc[key] = true;
-
-  return acc;
-}, {});
 
 const descriptionCollapsed = ref(true);
 
 const query = ref('');
 const pinnedBlocks = ref([]);
-const expandList = ref(categoriesExpand);
 
 const blocks = computed(() =>
   blocksArr.reduce((arr, block) => {
     if (
       block.name.toLocaleLowerCase().includes(query.value.toLocaleLowerCase())
     ) {
-      expandList.value[block.category] = true;
       (arr[block.category] = arr[block.category] || []).push(block);
     }
 
     return arr;
   }, {})
 );
+const pinnedBlocksList = computed(() =>
+  pinnedBlocks.value
+    .map((id) => ({ ...tasks[id], id, name: t(`workflow.blocks.${id}.name`) }))
+    .filter(({ name }) =>
+      name.toLocaleLowerCase().includes(query.value.toLocaleLowerCase())
+    )
+);
 
 function updateWorkflowIcon(value) {
   if (!value.startsWith('http')) return;
@@ -201,18 +161,6 @@ function updateWorkflowIcon(value) {
 
   emit('update', { icon: iconUrl });
 }
-function getBlockTitle({ description, id }) {
-  const blockPath = `workflow.blocks.${id}`;
-  let blockDescription = t(
-    `${blockPath}.${description ? 'description' : 'name'}`
-  );
-
-  if (description) {
-    blockDescription = `[${t(`${blockPath}.name`)}]\n${blockDescription}`;
-  }
-
-  return blockDescription;
-}
 function pinBlock({ id }) {
   const index = pinnedBlocks.value.indexOf(id);
 
@@ -220,6 +168,16 @@ function pinBlock({ id }) {
   else pinnedBlocks.value.push(id);
 }
 
+watch(
+  pinnedBlocks,
+  () => {
+    browser.storage.local.set({
+      pinnedBlocks: toRaw(pinnedBlocks.value),
+    });
+  },
+  { deep: true }
+);
+
 onMounted(() => {
   browser.storage.local.get('pinnedBlocks').then((item) => {
     pinnedBlocks.value = item.pinnedBlocks || [];