瀏覽代碼

feat: add custom workflow icon input

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

+ 9 - 1
src/components/newtab/shared/SharedCard.vue

@@ -3,7 +3,14 @@
     <slot name="header">
       <div class="flex items-center mb-4">
         <span class="p-2 rounded-lg bg-box-transparent">
-          <v-remixicon :name="data.icon || icon" />
+          <ui-img
+            v-if="data.icon.startsWith('http')"
+            :src="data.icon"
+            class="overflow-hidden rounded-lg"
+            style="height: 40px; width: 40px"
+            alt="Can not display"
+          />
+          <v-remixicon v-else :name="data.icon || icon" />
         </span>
         <div class="flex-grow"></div>
         <button
@@ -62,6 +69,7 @@ const props = defineProps({
     default: () => [],
   },
 });
+
 defineEmits(['execute', 'click', 'menuSelected']);
 
 let formattedDate = null;

+ 34 - 13
src/components/newtab/workflow/WorkflowDetailsCard.vue

@@ -1,24 +1,45 @@
 <template>
   <div class="px-4 flex items-center mb-2 mt-1">
-    <ui-popover class="mr-2 h-6">
+    <ui-popover class="mr-2 h-8">
       <template #trigger>
         <span
           :title="t('workflow.sidebar.workflowIcon')"
-          class="cursor-pointer"
+          class="cursor-pointer inline-block h-full"
         >
-          <v-remixicon :name="workflow.icon" size="26" />
+          <ui-img
+            v-if="workflow.icon.startsWith('http')"
+            :src="workflow.icon"
+            class="w-8 h-8"
+          />
+          <v-remixicon v-else :name="workflow.icon" size="26" class="mt-1" />
         </span>
       </template>
-      <p class="mb-2">{{ t('workflow.sidebar.workflowIcon') }}</p>
-      <div class="grid grid-cols-4 gap-1">
-        <span
-          v-for="icon in icons"
-          :key="icon"
-          class="cursor-pointer rounded-lg inline-block p-2 hoverable"
-          @click="$emit('update', { icon })"
-        >
-          <v-remixicon :name="icon" />
-        </span>
+      <div class="w-56">
+        <p class="mb-2">{{ t('workflow.sidebar.workflowIcon') }}</p>
+        <div class="grid grid-cols-5 mb-2 gap-1">
+          <span
+            v-for="icon in icons"
+            :key="icon"
+            class="
+              cursor-pointer
+              rounded-lg
+              inline-block
+              text-center
+              p-2
+              hoverable
+            "
+            @click="$emit('update', { icon })"
+          >
+            <v-remixicon :name="icon" />
+          </span>
+        </div>
+        <ui-input
+          :model-value="workflow.icon.startsWith('ri') ? '' : workflow.icon"
+          type="url"
+          placeholder="http://example.com/img.png"
+          label="Icon URL"
+          @change="$emit('update', { icon: $event })"
+        />
       </div>
     </ui-popover>
     <p class="font-semibold text-overflow inline-block text-lg flex-1 mr-4">

+ 91 - 0
src/components/ui/UiImg.vue

@@ -0,0 +1,91 @@
+<template>
+  <div ref="imageContainer" class="ui-image relative">
+    <div class="flex justify-center items-center">
+      <slot v-if="state.loading" name="loading">
+        <div
+          class="absolute h-full rounded-lg bg-input-dark w-full animate-pulse"
+        ></div>
+      </slot>
+      <slot v-else-if="state.error" name="error">
+        <p class="text-lighter text-center">Failed to load image</p>
+      </slot>
+      <div
+        v-else
+        :style="{
+          backgroundImage: `url(${src})`,
+          backgroundSize: contain ? 'contain' : 'cover',
+        }"
+        v-bind="{ role: alt ? 'img' : null, 'aria-label': alt }"
+        class="h-full absolute top-0 left-0 w-full bg-no-repeat bg-center"
+      >
+        <slot></slot>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import { ref, shallowReactive, onMounted } from 'vue';
+
+export default {
+  props: {
+    src: {
+      type: String,
+      default: '',
+    },
+    alt: {
+      type: String,
+      default: '',
+    },
+    lazy: Boolean,
+    contain: Boolean,
+  },
+  emits: ['error', 'load'],
+  setup(props, { emit }) {
+    const imageContainer = ref(null);
+    const state = shallowReactive({
+      loading: true,
+      error: false,
+    });
+
+    function handleImageLoad() {
+      state.loading = false;
+      state.error = false;
+
+      emit('load', true);
+    }
+    function handleImageError() {
+      state.loading = false;
+      state.error = true;
+
+      emit('error', true);
+    }
+    function loadImage() {
+      const image = new Image();
+
+      image.onload = () => handleImageLoad(image);
+      image.onerror = handleImageError;
+      image.src = props.src;
+    }
+
+    const observer = new IntersectionObserver((entries) => {
+      entries.forEach((entry) => {
+        if (entry.isIntersecting) {
+          const { target } = entry;
+          loadImage();
+          observer.unobserve(target);
+        }
+      });
+    });
+
+    onMounted(() => {
+      if (props.lazy) observer.observe(imageContainer.value);
+      else loadImage();
+    });
+
+    return {
+      state,
+      imageContainer,
+    };
+  },
+};
+</script>

+ 12 - 6
src/newtab/pages/Workflows.vue

@@ -54,12 +54,18 @@
       >
         <template #header>
           <div class="flex items-center mb-4">
-            <span
-              v-if="!workflow.isDisabled"
-              class="p-2 rounded-lg bg-box-transparent"
-            >
-              <v-remixicon :name="workflow.icon || icon" />
-            </span>
+            <template v-if="!workflow.isDisabled">
+              <ui-img
+                v-if="workflow.icon.startsWith('http')"
+                :src="workflow.icon"
+                class="rounded-lg overflow-hidden"
+                style="height: 40px; width: 40px"
+                alt="Can not display"
+              />
+              <span v-else class="p-2 rounded-lg bg-box-transparent">
+                <v-remixicon :name="workflow.icon" />
+              </span>
+            </template>
             <p v-else class="py-2">{{ t('common.disabled') }}</p>
             <div class="flex-grow"></div>
             <button