mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-24 08:44:06 +00:00
This pull request refactors and simplifies the template workflow card components and related UI in the codebase. The main changes focus on removing unused or redundant components, improving visual and interaction consistency, and enhancing error handling for images. Below are the most important changes grouped by theme: **Template Workflow Card Refactor and Cleanup** * Removed the `TemplateWorkflowCard.vue` component and its associated test file `TemplateWorkflowCard.spec.ts`, as well as the `TemplateWorkflowCardSkeleton.vue` and `TemplateWorkflowList.vue` components, indicating a shift away from the previous card-based template workflow UI. [[1]](diffhunk://#diff-49569af0404058e8257f3cc0716b066517ce7397dd58744b02aa0d0c61f2a815L1-L139) [[2]](diffhunk://#diff-9fa6fc1470371f0b520d4deda4129fb313b1bea69888a376556f4bd824f9d751L1-L263) [[3]](diffhunk://#diff-bc35b6f77d1cee6e86b05d0da80b7bd40013c7a6a97a89706d3bc52573e1c574L1-L30) [[4]](diffhunk://#diff-48171f792b22022526fca411d3c3a366d48b675dab77943a20846ae079cbaf3bL1-L68) * Removed the `TemplateSearchBar.vue` component, suggesting a redesign or replacement of the search/filter UI for templates. **UI and Interaction Improvements** * Improved the `CardBottom.vue` component by making its height configurable via a `fullHeight` prop, enhancing layout flexibility. * Updated the `CardContainer.vue` component to add hover effects (background, border, shadow, and padding) and support a new `none` aspect ratio for more flexible card layouts. **Image and Input Enhancements** * Enhanced the `LazyImage.vue` component to display a default placeholder image when an image fails to load, improving error handling and user experience. * Improved the `SearchBox.vue` component by making the input focusable when clicking anywhere on the wrapper, and added a template ref for better accessibility and usability. [[1]](diffhunk://#diff-08f3b0c51fbfe63171509b9944bf7558228f6c2596a1ef5338e88ab64585791bL2-R5) [[2]](diffhunk://#diff-08f3b0c51fbfe63171509b9944bf7558228f6c2596a1ef5338e88ab64585791bL16-R17) [[3]](diffhunk://#diff-08f3b0c51fbfe63171509b9944bf7558228f6c2596a1ef5338e88ab64585791bR33-R39) **Minor UI Tweaks** * Adjusted label styling in `SingleSelect.vue` to remove unnecessary overflow handling, simplifying the visual layout. --------- Co-authored-by: Benjamin Lu <benceruleanlu@proton.me> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: snomiao <snomiao@gmail.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com> Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com> Co-authored-by: Jin Yi <jin12cc@gmail.com>
131 lines
3.1 KiB
Vue
131 lines
3.1 KiB
Vue
<template>
|
|
<div
|
|
ref="containerRef"
|
|
class="relative overflow-hidden w-full h-full flex items-center justify-center"
|
|
>
|
|
<Skeleton
|
|
v-if="!isImageLoaded"
|
|
width="100%"
|
|
height="100%"
|
|
class="absolute inset-0"
|
|
/>
|
|
<img
|
|
v-if="cachedSrc"
|
|
ref="imageRef"
|
|
:src="cachedSrc"
|
|
:alt="alt"
|
|
draggable="false"
|
|
:class="imageClass"
|
|
:style="imageStyle"
|
|
@load="onImageLoad"
|
|
@error="onImageError"
|
|
/>
|
|
<div
|
|
v-if="hasError"
|
|
class="absolute inset-0 flex items-center justify-center bg-surface-50 dark-theme:bg-surface-800 text-muted"
|
|
>
|
|
<img
|
|
src="/assets/images/default-template.png"
|
|
:alt="alt"
|
|
draggable="false"
|
|
:class="imageClass"
|
|
:style="imageStyle"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import Skeleton from 'primevue/skeleton'
|
|
import { computed, onUnmounted, ref, watch } from 'vue'
|
|
|
|
import { useIntersectionObserver } from '@/composables/useIntersectionObserver'
|
|
import { useMediaCache } from '@/services/mediaCacheService'
|
|
|
|
const {
|
|
src,
|
|
alt = '',
|
|
imageClass = '',
|
|
imageStyle,
|
|
rootMargin = '300px'
|
|
} = defineProps<{
|
|
src: string
|
|
alt?: string
|
|
imageClass?: string | string[] | Record<string, boolean>
|
|
imageStyle?: Record<string, any>
|
|
rootMargin?: string
|
|
}>()
|
|
|
|
const containerRef = ref<HTMLElement | null>(null)
|
|
const imageRef = ref<HTMLImageElement | null>(null)
|
|
const isIntersecting = ref(false)
|
|
const isImageLoaded = ref(false)
|
|
const hasError = ref(false)
|
|
const cachedSrc = ref<string | undefined>(undefined)
|
|
|
|
const { getCachedMedia, acquireUrl, releaseUrl } = useMediaCache()
|
|
|
|
// Use intersection observer to detect when the image container comes into view
|
|
useIntersectionObserver(
|
|
containerRef,
|
|
(entries) => {
|
|
const entry = entries[0]
|
|
isIntersecting.value = entry?.isIntersecting ?? false
|
|
},
|
|
{
|
|
rootMargin,
|
|
threshold: 0.1
|
|
}
|
|
)
|
|
|
|
// Only start loading the image when it's in view
|
|
const shouldLoad = computed(() => isIntersecting.value)
|
|
|
|
watch(
|
|
shouldLoad,
|
|
async (shouldLoadVal) => {
|
|
if (shouldLoadVal && src && !cachedSrc.value && !hasError.value) {
|
|
try {
|
|
const cachedMedia = await getCachedMedia(src)
|
|
if (cachedMedia.error) {
|
|
hasError.value = true
|
|
} else if (cachedMedia.objectUrl) {
|
|
const acquiredUrl = acquireUrl(src)
|
|
cachedSrc.value = acquiredUrl || cachedMedia.objectUrl
|
|
} else {
|
|
cachedSrc.value = src
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to load cached media:', error)
|
|
cachedSrc.value = src
|
|
}
|
|
} else if (!shouldLoadVal) {
|
|
if (cachedSrc.value?.startsWith('blob:')) {
|
|
releaseUrl(src)
|
|
}
|
|
// Hide image when out of view
|
|
isImageLoaded.value = false
|
|
cachedSrc.value = undefined
|
|
hasError.value = false
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
const onImageLoad = () => {
|
|
isImageLoaded.value = true
|
|
hasError.value = false
|
|
}
|
|
|
|
const onImageError = () => {
|
|
hasError.value = true
|
|
isImageLoaded.value = false
|
|
}
|
|
|
|
onUnmounted(() => {
|
|
if (cachedSrc.value?.startsWith('blob:')) {
|
|
releaseUrl(src)
|
|
}
|
|
})
|
|
</script>
|