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: add useIntersectionObserver, useLazyPagination, useTemplateFilt…
…ering, and mediaCacheService for improved component functionality
  • Loading branch information
Myestery committed Jul 30, 2025
commit 6b69225bbf36a62f599fed1e5cb2ad450274beee
60 changes: 60 additions & 0 deletions src/composables/useIntersectionObserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { type Ref, onBeforeUnmount, ref, watch } from 'vue'

export interface UseIntersectionObserverOptions
extends IntersectionObserverInit {
immediate?: boolean
}

export function useIntersectionObserver(
target: Ref<Element | null>,
callback: IntersectionObserverCallback,
options: UseIntersectionObserverOptions = {}
) {
const { immediate = true, ...observerOptions } = options

const isSupported =
typeof window !== 'undefined' && 'IntersectionObserver' in window
const isIntersecting = ref(false)

let observer: IntersectionObserver | null = null

const cleanup = () => {
if (observer) {
observer.disconnect()
observer = null
}
}

const observe = () => {
cleanup()

if (!isSupported || !target.value) return

observer = new IntersectionObserver((entries) => {
isIntersecting.value = entries.some((entry) => entry.isIntersecting)
callback(entries, observer!)
}, observerOptions)

observer.observe(target.value)
}

const unobserve = () => {
if (observer && target.value) {
observer.unobserve(target.value)
}
}

if (immediate) {
watch(target, observe, { immediate: true, flush: 'post' })
}

onBeforeUnmount(cleanup)

return {
isSupported,
isIntersecting,
observe,
unobserve,
cleanup
}
}
107 changes: 107 additions & 0 deletions src/composables/useLazyPagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { type Ref, computed, ref, shallowRef, watch } from 'vue'

export interface LazyPaginationOptions {
itemsPerPage?: number
initialPage?: number
}

export function useLazyPagination<T>(
items: Ref<T[]> | T[],
options: LazyPaginationOptions = {}
) {
const { itemsPerPage = 12, initialPage = 1 } = options

const currentPage = ref(initialPage)
const isLoading = ref(false)
const loadedPages = shallowRef(new Set<number>([]))

// Get reactive items array
const itemsArray = computed(() => {
const itemData = 'value' in items ? items.value : items
return Array.isArray(itemData) ? itemData : []
})

// Simulate pagination by slicing the items
const paginatedItems = computed(() => {
const itemData = itemsArray.value
if (itemData.length === 0) {
return []
}

const loadedPageNumbers = Array.from(loadedPages.value).sort(
(a, b) => a - b
)
const maxLoadedPage = Math.max(...loadedPageNumbers, 0)
const endIndex = maxLoadedPage * itemsPerPage
return itemData.slice(0, endIndex)
})

const hasMoreItems = computed(() => {
const itemData = itemsArray.value
if (itemData.length === 0) {
return false
}

const loadedPagesArray = Array.from(loadedPages.value)
const maxLoadedPage = Math.max(...loadedPagesArray, 0)
return maxLoadedPage * itemsPerPage < itemData.length
})

const totalPages = computed(() => {
const itemData = itemsArray.value
if (itemData.length === 0) {
return 0
}
return Math.ceil(itemData.length / itemsPerPage)
})

const loadNextPage = async () => {
if (isLoading.value || !hasMoreItems.value) return

isLoading.value = true
const loadedPagesArray = Array.from(loadedPages.value)
const nextPage = Math.max(...loadedPagesArray, 0) + 1

// Simulate network delay
// await new Promise((resolve) => setTimeout(resolve, 5000))

const newLoadedPages = new Set(loadedPages.value)
newLoadedPages.add(nextPage)
loadedPages.value = newLoadedPages
currentPage.value = nextPage
isLoading.value = false
}

// Initialize with first page
watch(
() => itemsArray.value.length,
(length) => {
if (length > 0 && loadedPages.value.size === 0) {
loadedPages.value = new Set([1])
}
},
{ immediate: true }
)

const reset = () => {
currentPage.value = initialPage
loadedPages.value = new Set([])
isLoading.value = false

// Immediately load first page if we have items
const itemData = itemsArray.value
if (itemData.length > 0) {
loadedPages.value = new Set([1])
}
}

return {
paginatedItems,
isLoading,
hasMoreItems,
currentPage,
totalPages,
loadNextPage,
reset
}
}
56 changes: 56 additions & 0 deletions src/composables/useTemplateFiltering.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { type Ref, computed, ref } from 'vue'

import type { TemplateInfo } from '@/types/workflowTemplateTypes'

export interface TemplateFilterOptions {
searchQuery?: string
}

export function useTemplateFiltering(
templates: Ref<TemplateInfo[]> | TemplateInfo[]
) {
const searchQuery = ref('')

const templatesArray = computed(() => {
const templateData = 'value' in templates ? templates.value : templates
return Array.isArray(templateData) ? templateData : []
})

const filteredTemplates = computed(() => {
const templateData = templatesArray.value
if (templateData.length === 0) {
return []
}

if (!searchQuery.value.trim()) {
return templateData
}

const query = searchQuery.value.toLowerCase().trim()
return templateData.filter((template) => {
const searchableText = [
template.name,
template.description,
template.sourceModule
]
.filter(Boolean)
.join(' ')
.toLowerCase()

return searchableText.includes(query)
})
})

const resetFilters = () => {
searchQuery.value = ''
}

const filteredCount = computed(() => filteredTemplates.value.length)

return {
searchQuery,
filteredTemplates,
filteredCount,
resetFilters
}
}
Loading