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