Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: implement LazyImage component for thumbnails and search bar
  • Loading branch information
Myestery committed Jul 30, 2025
commit 0b2d985fbf92a74ad3f78f9759f342ffe11b6ab2
64 changes: 64 additions & 0 deletions src/components/templates/TemplateSearchBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<template>
<div class="relative w-full p-4">
<div class="h-12 flex items-center gap-4 justify-between">
<div class="flex-1 max-w-md">
<AutoComplete
v-model.lazy="searchQuery"
:placeholder="$t('templateWorkflows.searchPlaceholder')"
:complete-on-focus="false"
:delay="200"
class="w-full"
:pt="{
pcInputText: {
root: {
class: 'w-full rounded-2xl'
}
},
loader: {
style: 'display: none'
}
}"
:show-empty-message="false"
@complete="() => {}"
/>
</div>
</div>

<div class="flex items-center gap-4 mt-2">
<small
v-if="searchQuery && filteredCount !== null"
class="text-color-secondary"
>
{{ $t('g.resultsCount', { count: filteredCount }) }}
</small>
<Button
v-if="searchQuery"
text
size="small"
icon="pi pi-times"
:label="$t('g.clearFilters')"
@click="clearFilters"
/>
</div>
</div>
</template>

<script setup lang="ts">
import AutoComplete from 'primevue/autocomplete'
import Button from 'primevue/button'

const { filteredCount } = defineProps<{
filteredCount?: number | null
}>()

const searchQuery = defineModel<string>('searchQuery', { default: '' })

const emit = defineEmits<{
clearFilters: []
}>()

const clearFilters = () => {
searchQuery.value = ''
emit('clearFilters')
}
</script>
11 changes: 6 additions & 5 deletions src/components/templates/thumbnails/CompareSliderThumbnail.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
<template>
<BaseThumbnail :is-hovered="isHovered">
<img
<LazyImage
:src="baseImageSrc"
:alt="alt"
:class="
:image-class="
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
"
/>
<div ref="containerRef" class="absolute inset-0">
<img
<LazyImage
:src="overlayImageSrc"
:alt="alt"
:class="
:image-class="
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
"
:style="{
:image-style="{
clipPath: `inset(0 ${100 - sliderPosition}% 0 0)`
}"
/>
Expand All @@ -36,6 +36,7 @@
import { useMouseInElement } from '@vueuse/core'
import { ref, watch } from 'vue'

import LazyImage from '@/components/common/LazyImage.vue'
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'

const SLIDER_START_POSITION = 50
Expand Down
30 changes: 14 additions & 16 deletions src/components/templates/thumbnails/DefaultThumbnail.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
<template>
<BaseThumbnail :hover-zoom="hoverZoom" :is-hovered="isHovered">
<div class="overflow-hidden w-full h-full flex items-center justify-center">
<img
:src="src"
:alt="alt"
draggable="false"
:class="[
'transform-gpu transition-transform duration-300 ease-out',
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
]"
:style="
isHovered ? { transform: `scale(${1 + hoverZoom / 100})` } : undefined
"
/>
</div>
<LazyImage
:src="src"
:alt="alt"
:image-class="[
'transform-gpu transition-transform duration-300 ease-out',
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
]"
:image-style="
isHovered ? { transform: `scale(${1 + hoverZoom / 100})` } : undefined
"
/>
</BaseThumbnail>
</template>

<script setup lang="ts">
import LazyImage from '@/components/common/LazyImage.vue'
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'

const { src, isVideo } = defineProps<{
Expand Down
41 changes: 20 additions & 21 deletions src/components/templates/thumbnails/HoverDissolveThumbnail.vue
Original file line number Diff line number Diff line change
@@ -1,37 +1,23 @@
<template>
<BaseThumbnail :is-hovered="isHovered">
<div class="relative w-full h-full">
<img
:src="baseImageSrc"
:alt="alt"
draggable="false"
class="absolute inset-0"
:class="
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
"
/>
<img
<LazyImage :src="baseImageSrc" :alt="alt" :image-class="baseImageClass" />
<LazyImage
:src="overlayImageSrc"
:alt="alt"
draggable="false"
class="absolute inset-0 transition-opacity duration-300"
:class="[
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain',
{ 'opacity-100': isHovered, 'opacity-0': !isHovered }
]"
:image-class="overlayImageClass"
/>
</div>
</BaseThumbnail>
</template>

<script setup lang="ts">
import { computed } from 'vue'

import LazyImage from '@/components/common/LazyImage.vue'
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'

const { baseImageSrc, overlayImageSrc, isVideo } = defineProps<{
const { baseImageSrc, overlayImageSrc, isVideo, isHovered } = defineProps<{
baseImageSrc: string
overlayImageSrc: string
alt: string
Expand All @@ -44,4 +30,17 @@ const isVideoType =
baseImageSrc?.toLowerCase().endsWith('.webp') ||
overlayImageSrc?.toLowerCase().endsWith('.webp') ||
false

const baseImageClass = computed(() => {
return `absolute inset-0 ${isVideoType ? 'w-full h-full object-cover' : 'max-w-full max-h-64 object-contain'}`
})

const overlayImageClass = computed(() => {
const baseClasses = 'absolute inset-0 transition-opacity duration-300'
const sizeClasses = isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
const opacityClasses = isHovered ? 'opacity-100' : 'opacity-0'
return `${baseClasses} ${sizeClasses} ${opacityClasses}`
})
</script>