mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-11 02:20:08 +00:00
perf: virtualize FormDropdownMenu to reduce DOM nodes and image requests
- Integrate VirtualGrid into FormDropdownMenu for virtualized rendering - Only render visible items (~20-30) instead of all items (100+) - Add computed properties for grid configuration per layout mode - Extend VirtualGrid slot to provide original item index - Change container from max-h to fixed h for proper virtualization Amp-Thread-ID: https://ampcode.com/threads/T-019c0ca8-be8d-770e-ab31-349937cd2acf Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -6,12 +6,12 @@
|
||||
<div :style="topSpacerStyle" />
|
||||
<div :style="mergedGridStyle">
|
||||
<div
|
||||
v-for="item in renderedItems"
|
||||
v-for="(item, i) in renderedItems"
|
||||
:key="item.key"
|
||||
class="transition-[width] duration-150 ease-out"
|
||||
data-virtual-grid-item
|
||||
>
|
||||
<slot name="item" :item="item" />
|
||||
<slot name="item" :item="item" :index="state.start + i" />
|
||||
</div>
|
||||
</div>
|
||||
<div :style="bottomSpacerStyle" />
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { MaybeRefOrGetter } from 'vue'
|
||||
import type { CSSProperties, MaybeRefOrGetter } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
import VirtualGrid from '@/components/common/VirtualGrid.vue'
|
||||
|
||||
import FormDropdownMenuActions from './FormDropdownMenuActions.vue'
|
||||
import FormDropdownMenuFilter from './FormDropdownMenuFilter.vue'
|
||||
@@ -26,7 +27,7 @@ interface Props {
|
||||
updateKey?: MaybeRefOrGetter<unknown>
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'item-click', item: DropdownItem, index: number): void
|
||||
}>()
|
||||
@@ -37,12 +38,70 @@ const layoutMode = defineModel<LayoutMode>('layoutMode')
|
||||
const sortSelected = defineModel<OptionId>('sortSelected')
|
||||
const searchQuery = defineModel<string>('searchQuery')
|
||||
|
||||
// Handle item selection
|
||||
// VirtualGrid configuration based on layout mode
|
||||
const maxColumns = computed(() => {
|
||||
switch (layoutMode.value) {
|
||||
case 'grid':
|
||||
return 4
|
||||
case 'list':
|
||||
case 'list-small':
|
||||
return 1
|
||||
default:
|
||||
return 4
|
||||
}
|
||||
})
|
||||
|
||||
const defaultItemHeight = computed(() => {
|
||||
switch (layoutMode.value) {
|
||||
case 'grid':
|
||||
return 120
|
||||
case 'list':
|
||||
return 64
|
||||
case 'list-small':
|
||||
return 40
|
||||
default:
|
||||
return 120
|
||||
}
|
||||
})
|
||||
|
||||
const defaultItemWidth = computed(() => {
|
||||
switch (layoutMode.value) {
|
||||
case 'grid':
|
||||
return 89
|
||||
case 'list':
|
||||
case 'list-small':
|
||||
return 380
|
||||
default:
|
||||
return 89
|
||||
}
|
||||
})
|
||||
|
||||
const gridStyle = computed<CSSProperties>(() => ({
|
||||
display: 'grid',
|
||||
gridTemplateColumns:
|
||||
layoutMode.value === 'grid' ? 'repeat(4, 1fr)' : 'repeat(1, 1fr)',
|
||||
gap:
|
||||
layoutMode.value === 'grid'
|
||||
? '1rem 0.5rem'
|
||||
: layoutMode.value === 'list'
|
||||
? '0.5rem'
|
||||
: '0.25rem',
|
||||
padding: '1rem',
|
||||
width: '100%'
|
||||
}))
|
||||
|
||||
type VirtualDropdownItem = DropdownItem & { key: string }
|
||||
const virtualItems = computed<VirtualDropdownItem[]>(() =>
|
||||
props.items.map((item) => ({
|
||||
...item,
|
||||
key: String(item.id)
|
||||
}))
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex max-h-[640px] w-103 flex-col rounded-lg bg-component-node-background pt-4 outline outline-offset-[-1px] outline-node-component-border"
|
||||
class="flex h-[640px] w-103 flex-col rounded-lg bg-component-node-background pt-4 outline outline-offset-[-1px] outline-node-component-border"
|
||||
>
|
||||
<!-- Filter -->
|
||||
<FormDropdownMenuFilter
|
||||
@@ -59,45 +118,44 @@ const searchQuery = defineModel<string>('searchQuery')
|
||||
:searcher
|
||||
:update-key="updateKey"
|
||||
/>
|
||||
<!-- List -->
|
||||
<div class="relative flex h-full mt-2 overflow-y-scroll">
|
||||
<!-- List Container -->
|
||||
<div class="relative mt-2 min-h-0 flex-1">
|
||||
<!-- Empty State -->
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'h-full max-h-full grid gap-x-2 gap-y-4 overflow-y-auto px-4 pt-4 pb-4 w-full',
|
||||
{
|
||||
'grid-cols-4': layoutMode === 'grid',
|
||||
'grid-cols-1 gap-y-2': layoutMode === 'list',
|
||||
'grid-cols-1 gap-y-1': layoutMode === 'list-small'
|
||||
}
|
||||
)
|
||||
"
|
||||
v-if="items.length === 0"
|
||||
class="flex h-50 items-center justify-center"
|
||||
>
|
||||
<div class="pointer-events-none absolute inset-x-3 top-0 z-10 h-5" />
|
||||
<div
|
||||
v-if="items.length === 0"
|
||||
class="h-50 col-span-full flex items-center justify-center"
|
||||
>
|
||||
<i
|
||||
:title="$t('g.noItems')"
|
||||
:aria-label="$t('g.noItems')"
|
||||
class="icon-[lucide--circle-off] size-30 text-zinc-500/20"
|
||||
/>
|
||||
</div>
|
||||
<!-- Item -->
|
||||
<FormDropdownMenuItem
|
||||
v-for="(item, index) in items"
|
||||
:key="item.id"
|
||||
:index="index"
|
||||
:selected="isSelected(item, index)"
|
||||
:media-src="item.mediaSrc"
|
||||
:name="item.name"
|
||||
:label="item.label"
|
||||
:metadata="item.metadata"
|
||||
:layout="layoutMode"
|
||||
@click="emit('item-click', item, index)"
|
||||
<i
|
||||
:title="$t('g.noItems')"
|
||||
:aria-label="$t('g.noItems')"
|
||||
class="icon-[lucide--circle-off] size-30 text-zinc-500/20"
|
||||
/>
|
||||
</div>
|
||||
<!-- Virtualized Grid -->
|
||||
<VirtualGrid
|
||||
v-else
|
||||
:key="layoutMode"
|
||||
:items="virtualItems"
|
||||
:grid-style="gridStyle"
|
||||
:max-columns="maxColumns"
|
||||
:default-item-height="defaultItemHeight"
|
||||
:default-item-width="defaultItemWidth"
|
||||
:buffer-rows="2"
|
||||
class="h-full"
|
||||
>
|
||||
<template #item="{ item, index }">
|
||||
<FormDropdownMenuItem
|
||||
:index="index"
|
||||
:selected="isSelected(item, index)"
|
||||
:media-src="item.mediaSrc"
|
||||
:name="item.name"
|
||||
:label="item.label"
|
||||
:metadata="item.metadata"
|
||||
:layout="layoutMode"
|
||||
@click="emit('item-click', item, index)"
|
||||
/>
|
||||
</template>
|
||||
</VirtualGrid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user