Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Chenlei Hu
2025-02-28 14:11:14 -05:00
committed by GitHub
parent e106fded37
commit 24e560bb2d
82 changed files with 1662 additions and 106 deletions

View File

@@ -1,38 +1,45 @@
<template>
<Card :data-testid="`template-workflow-${template.name}`" class="w-64">
<Card
ref="cardRef"
:data-testid="`template-workflow-${template.name}`"
class="w-64 group"
>
<template #header>
<div class="flex items-center justify-center">
<div class="relative overflow-hidden rounded-t-lg cursor-pointer">
<div
class="flex items-center justify-center cursor-pointer"
@click="$emit('loadWorkflow', template.name)"
>
<div class="relative overflow-hidden rounded-t-lg">
<template v-if="template.mediaType === 'audio'">
<div class="w-64 h-64 flex items-center justify-center p-4 z-20">
<audio
controls
class="w-full relative z-20"
:src="thumbnailSrc"
@error="imageError = true"
@click.stop
/>
</div>
<AudioThumbnail :src="baseThumbnailSrc" />
</template>
<template v-else-if="template.thumbnailVariant === 'compareSlider'">
<CompareSliderThumbnail
:base-image-src="baseThumbnailSrc"
:overlay-image-src="overlayThumbnailSrc"
:alt="title"
:is-hovered="isHovered"
/>
</template>
<template v-else-if="template.thumbnailVariant === 'hoverDissolve'">
<HoverDissolveThumbnail
:base-image-src="baseThumbnailSrc"
:overlay-image-src="overlayThumbnailSrc"
:alt="title"
:is-hovered="isHovered"
/>
</template>
<template v-else>
<img
v-if="!imageError"
:src="thumbnailSrc"
<DefaultThumbnail
:src="baseThumbnailSrc"
:alt="title"
class="w-64 h-64 rounded-t-lg object-cover thumbnail"
@error="imageError = true"
:hover-zoom="
template.thumbnailVariant === 'zoomHover'
? UPSCALE_ZOOM_SCALE
: DEFAULT_ZOOM_SCALE
"
/>
<div v-else class="w-64 h-64 content-center text-center">
<i class="pi pi-file" style="font-size: 4rem"></i>
</div>
</template>
<a @click="$emit('loadWorkflow', template.name)">
<div
class="absolute top-0 left-0 w-64 h-64 overflow-hidden opacity-0 transition duration-300 ease-in-out hover:opacity-100 bg-opacity-50 bg-black flex items-center justify-center z-10"
>
<i class="pi pi-play-circle" style="color: white"></i>
</div>
</a>
<ProgressSpinner
v-if="loading"
class="absolute inset-0 z-1 w-3/12 h-full"
@@ -41,7 +48,9 @@
</div>
</template>
<template #subtitle>
<div class="text-center">
<div
class="text-center py-2 opacity-85 group-hover:opacity-100 transition-opacity"
>
{{ title }}
</div>
</template>
@@ -49,14 +58,22 @@
</template>
<script setup lang="ts">
import { useElementHover } from '@vueuse/core'
import Card from 'primevue/card'
import ProgressSpinner from 'primevue/progressspinner'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import AudioThumbnail from '@/components/templates/thumbnails/AudioThumbnail.vue'
import CompareSliderThumbnail from '@/components/templates/thumbnails/CompareSliderThumbnail.vue'
import DefaultThumbnail from '@/components/templates/thumbnails/DefaultThumbnail.vue'
import HoverDissolveThumbnail from '@/components/templates/thumbnails/HoverDissolveThumbnail.vue'
import { TemplateInfo } from '@/types/workflowTemplateTypes'
import { normalizeI18nKey } from '@/utils/formatUtil'
const UPSCALE_ZOOM_SCALE = 38 // for upscale templates, exaggerate the hover zoom
const DEFAULT_ZOOM_SCALE = 6
const { sourceModule, categoryTitle, loading, template } = defineProps<{
sourceModule: string
categoryTitle: string
@@ -66,13 +83,23 @@ const { sourceModule, categoryTitle, loading, template } = defineProps<{
const { t } = useI18n()
const imageError = ref(false)
const cardRef = ref<HTMLElement | null>(null)
const isHovered = useElementHover(cardRef)
const thumbnailSrc = computed(() =>
sourceModule === 'default'
? `/templates/${template.name}.${template.mediaSubtype}`
: `/api/workflow_templates/${sourceModule}/${template.name}.${template.mediaSubtype}`
? `/templates/${template.name}`
: `/api/workflow_templates/${sourceModule}/${template.name}`
)
const baseThumbnailSrc = computed(
() => `${thumbnailSrc.value}-1.${template.mediaSubtype}`
)
const overlayThumbnailSrc = computed(
() => `${thumbnailSrc.value}-2.${template.mediaSubtype}`
)
const title = computed(() => {
return sourceModule === 'default'
? t(

View File

@@ -0,0 +1,15 @@
<template>
<BaseThumbnail>
<div class="w-64 h-64 flex items-center justify-center p-4">
<audio controls class="w-full relative" :src="src" @click.stop />
</div>
</BaseThumbnail>
</template>
<script setup lang="ts">
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'
defineProps<{
src: string
}>()
</script>

View File

@@ -0,0 +1,30 @@
<template>
<div class="relative w-64 h-64 rounded-t-lg overflow-hidden select-none">
<div v-if="!error" ref="contentRef">
<slot />
</div>
<div
v-else
class="w-full h-full flex items-center justify-center bg-surface-card"
>
<i class="pi pi-file text-4xl text-surface-600" />
</div>
</div>
</template>
<script setup lang="ts">
import { useEventListener } from '@vueuse/core'
import { onMounted, ref } from 'vue'
const error = ref(false)
const contentRef = ref<HTMLElement | null>(null)
onMounted(() => {
const images = Array.from(contentRef.value?.getElementsByTagName('img') ?? [])
images.forEach((img) => {
useEventListener(img, 'error', () => {
error.value = true
})
})
})
</script>

View File

@@ -0,0 +1,51 @@
<template>
<BaseThumbnail>
<img :src="baseImageSrc" :alt="alt" class="w-full h-full object-cover" />
<div ref="containerRef" class="absolute inset-0">
<img
:src="overlayImageSrc"
:alt="alt"
class="w-full h-full object-cover"
:style="{
clipPath: `inset(0 ${100 - sliderPosition}% 0 0)`
}"
/>
<div
class="absolute inset-y-0 w-0.5 bg-white/30 backdrop-blur-sm z-10 pointer-events-none"
:style="{
left: `${sliderPosition}%`
}"
/>
</div>
</BaseThumbnail>
</template>
<script setup lang="ts">
import { useMouseInElement } from '@vueuse/core'
import { ref, watch } from 'vue'
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'
const { isHovered } = defineProps<{
baseImageSrc: string
overlayImageSrc: string
alt: string
isHovered?: boolean
}>()
const sliderPosition = ref(21)
const containerRef = ref<HTMLElement | null>(null)
const { elementX, elementWidth, isOutside } = useMouseInElement(containerRef)
// Update slider position based on mouse position when hovered
watch(
[() => isHovered, elementX, elementWidth, isOutside],
([isHovered, x, width, outside]) => {
if (!isHovered) return
if (!outside) {
sliderPosition.value = (x / width) * 100
}
}
)
</script>

View File

@@ -0,0 +1,37 @@
<template>
<BaseThumbnail>
<div ref="containerRef" class="overflow-hidden">
<img
:src="src"
:alt="alt"
draggable="false"
class="w-64 h-64 object-cover transform-gpu transition-transform duration-300 ease-out"
:style="
isHovered ? { transform: `scale(${1 + hoverZoom / 100})` } : undefined
"
/>
</div>
</BaseThumbnail>
</template>
<script setup lang="ts">
import { useElementHover } from '@vueuse/core'
import { ref } from 'vue'
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'
const { hoverZoom = 8 } = defineProps<{
src: string
alt: string
hoverZoom?: number
}>()
const containerRef = ref<HTMLElement | null>(null)
const isHovered = useElementHover(containerRef)
</script>
<style scoped>
img {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<BaseThumbnail>
<div class="relative w-full h-full">
<img
:src="baseImageSrc"
:alt="alt"
draggable="false"
class="absolute inset-0 w-64 h-64 object-cover"
/>
<img
:src="overlayImageSrc"
:alt="alt"
draggable="false"
class="absolute inset-0 w-64 h-64 object-cover transition-opacity duration-300"
:class="{ 'opacity-100': isHovered, 'opacity-0': !isHovered }"
/>
</div>
</BaseThumbnail>
</template>
<script setup lang="ts">
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'
defineProps<{
baseImageSrc: string
overlayImageSrc: string
alt: string
isHovered: boolean
}>()
</script>