mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-19 22:34:15 +00:00
[Manager] Add infinite scroll to search results (#3124)
This commit is contained in:
@@ -15,10 +15,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T">
|
||||
import { useElementSize, useScroll } from '@vueuse/core'
|
||||
import { useElementSize, useScroll, whenever } from '@vueuse/core'
|
||||
import { clamp, debounce } from 'lodash'
|
||||
import { type CSSProperties, computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||
|
||||
type GridState = {
|
||||
start: number
|
||||
end: number
|
||||
isNearEnd: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
items,
|
||||
bufferRows = 1,
|
||||
@@ -36,6 +42,13 @@ const {
|
||||
defaultItemWidth?: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
/**
|
||||
* Emitted when `bufferRows` (or fewer) rows remaining between scrollY and grid bottom.
|
||||
*/
|
||||
'approach-end': []
|
||||
}>()
|
||||
|
||||
const itemHeight = ref(defaultItemHeight)
|
||||
const itemWidth = ref(defaultItemWidth)
|
||||
const container = ref<HTMLElement | null>(null)
|
||||
@@ -50,22 +63,31 @@ const viewRows = computed(() => Math.ceil(height.value / itemHeight.value))
|
||||
const offsetRows = computed(() => Math.floor(scrollY.value / itemHeight.value))
|
||||
const isValidGrid = computed(() => height.value && width.value && items?.length)
|
||||
|
||||
const state = computed<{ start: number; end: number }>(() => {
|
||||
const state = computed<GridState>(() => {
|
||||
const fromRow = offsetRows.value - bufferRows
|
||||
const toRow = offsetRows.value + bufferRows + viewRows.value
|
||||
|
||||
const fromCol = fromRow * cols.value
|
||||
const toCol = toRow * cols.value
|
||||
const remainingCol = items.length - toCol
|
||||
|
||||
return {
|
||||
start: clamp(fromCol, 0, items?.length),
|
||||
end: clamp(toCol, fromCol, items?.length)
|
||||
end: clamp(toCol, fromCol, items?.length),
|
||||
isNearEnd: remainingCol <= cols.value * bufferRows
|
||||
}
|
||||
})
|
||||
const renderedItems = computed(() =>
|
||||
isValidGrid.value ? items.slice(state.value.start, state.value.end) : []
|
||||
)
|
||||
|
||||
whenever(
|
||||
() => state.value.isNearEnd,
|
||||
() => {
|
||||
emit('approach-end')
|
||||
}
|
||||
)
|
||||
|
||||
const updateItemSize = () => {
|
||||
if (container.value) {
|
||||
const firstItem = container.value.querySelector('[data-virtual-grid-item]')
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
/>
|
||||
<div class="flex-1 overflow-auto">
|
||||
<div
|
||||
v-if="isLoading || isInitialLoad"
|
||||
v-if="(searchResults.length === 0 && isLoading) || isInitialLoad"
|
||||
class="flex justify-center items-center h-full"
|
||||
>
|
||||
<ProgressSpinner />
|
||||
@@ -55,13 +55,14 @@
|
||||
<div v-else class="h-full" @click="handleGridContainerClick">
|
||||
<VirtualGrid
|
||||
:items="resultsWithKeys"
|
||||
:buffer-rows="5"
|
||||
:buffer-rows="3"
|
||||
:gridStyle="{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(22rem, 1fr))',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(19rem, 1fr))',
|
||||
padding: '0.5rem',
|
||||
gap: '1.5rem'
|
||||
}"
|
||||
@approach-end="onApproachEnd"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<PackCard
|
||||
@@ -138,6 +139,9 @@ const {
|
||||
suggestions
|
||||
} = useRegistrySearch()
|
||||
pageNumber.value = 0
|
||||
const onApproachEnd = () => {
|
||||
pageNumber.value++
|
||||
}
|
||||
|
||||
const isInitialLoad = computed(
|
||||
() => searchResults.value.length === 0 && searchQuery.value === ''
|
||||
|
||||
@@ -49,8 +49,11 @@ export function useRegistrySearch() {
|
||||
(algoliaNode: AlgoliaNodePack) => algoliaNode.id
|
||||
)
|
||||
|
||||
const onQueryChange = async () => {
|
||||
const updateSearchResults = async (options: { append?: boolean }) => {
|
||||
isLoading.value = true
|
||||
if (!options.append) {
|
||||
pageNumber.value = 0
|
||||
}
|
||||
const { nodePacks, querySuggestions } = await searchPacks(
|
||||
searchQuery.value,
|
||||
{
|
||||
@@ -59,16 +62,23 @@ export function useRegistrySearch() {
|
||||
restrictSearchableAttributes: searchAttributes.value
|
||||
}
|
||||
)
|
||||
results.value = nodePacks
|
||||
if (options.append && results.value?.length) {
|
||||
results.value = results.value.concat(nodePacks)
|
||||
} else {
|
||||
results.value = nodePacks
|
||||
}
|
||||
suggestions.value = querySuggestions
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
watch([pageNumber, sortField, searchMode], onQueryChange, {
|
||||
immediate: true
|
||||
})
|
||||
const onQueryChange = () => updateSearchResults({ append: false })
|
||||
const onPageChange = () => updateSearchResults({ append: true })
|
||||
|
||||
watch([sortField, searchMode], onQueryChange)
|
||||
watch(pageNumber, onPageChange)
|
||||
watchDebounced(searchQuery, onQueryChange, {
|
||||
debounce: SEARCH_DEBOUNCE_TIME
|
||||
debounce: SEARCH_DEBOUNCE_TIME,
|
||||
immediate: true
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user