diff --git a/src/components/common/LazyImage.vue b/src/components/common/LazyImage.vue new file mode 100644 index 000000000..1e7fced24 --- /dev/null +++ b/src/components/common/LazyImage.vue @@ -0,0 +1,113 @@ + + + diff --git a/src/components/templates/TemplateWorkflowCardSkeleton.vue b/src/components/templates/TemplateWorkflowCardSkeleton.vue new file mode 100644 index 000000000..00bf73839 --- /dev/null +++ b/src/components/templates/TemplateWorkflowCardSkeleton.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/components/templates/TemplateWorkflowView.vue b/src/components/templates/TemplateWorkflowView.vue index 174a91201..8a866cdd1 100644 --- a/src/components/templates/TemplateWorkflowView.vue +++ b/src/components/templates/TemplateWorkflowView.vue @@ -1,24 +1,31 @@ @@ -54,12 +78,21 @@ import { useLocalStorage } from '@vueuse/core' import DataView from 'primevue/dataview' import SelectButton from 'primevue/selectbutton' +import { computed, ref, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import TemplateSearchBar from '@/components/templates/TemplateSearchBar.vue' import TemplateWorkflowCard from '@/components/templates/TemplateWorkflowCard.vue' +import TemplateWorkflowCardSkeleton from '@/components/templates/TemplateWorkflowCardSkeleton.vue' import TemplateWorkflowList from '@/components/templates/TemplateWorkflowList.vue' +import { useIntersectionObserver } from '@/composables/useIntersectionObserver' +import { useLazyPagination } from '@/composables/useLazyPagination' +import { useTemplateFiltering } from '@/composables/useTemplateFiltering' import type { TemplateInfo } from '@/types/workflowTemplateTypes' -defineProps<{ +const { t } = useI18n() + +const { title, sourceModule, categoryTitle, loading, templates } = defineProps<{ title: string sourceModule: string categoryTitle: string @@ -72,6 +105,59 @@ const layout = useLocalStorage<'grid' | 'list'>( 'grid' ) +const skeletonCount = 6 +const loadTrigger = ref(null) + +const templatesRef = computed(() => templates || []) + +const { searchQuery, filteredTemplates, filteredCount } = + useTemplateFiltering(templatesRef) + +// When searching, show all results immediately without pagination +// When not searching, use lazy pagination +const shouldUsePagination = computed(() => !searchQuery.value.trim()) + +// Lazy pagination setup using filtered templates +const { + paginatedItems: paginatedTemplates, + isLoading: isLoadingMore, + hasMoreItems: hasMoreTemplates, + loadNextPage, + reset +} = useLazyPagination(filteredTemplates, { + itemsPerPage: 12 +}) + +// Final templates to display +const displayTemplates = computed(() => { + return shouldUsePagination.value + ? paginatedTemplates.value + : filteredTemplates.value +}) +// Intersection observer for auto-loading (only when not searching) +useIntersectionObserver( + loadTrigger, + (entries) => { + const entry = entries[0] + if ( + entry?.isIntersecting && + shouldUsePagination.value && + hasMoreTemplates.value && + !isLoadingMore.value + ) { + void loadNextPage() + } + }, + { + rootMargin: '200px', + threshold: 0.1 + } +) + +watch([() => templates, searchQuery], () => { + reset() +}) + const emit = defineEmits<{ loadWorkflow: [name: string] }>()