Skip to content

Commit e155095

Browse files
authored
fix(rolldown): plugin details virtual list scrolling (#287)
1 parent 6d1e13d commit e155095

File tree

3 files changed

+130
-111
lines changed

3 files changed

+130
-111
lines changed

packages/rolldown/src/app/components/data/PluginDetailsTable.vue

Lines changed: 106 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -82,108 +82,119 @@ function toggleDurationSortType() {
8282
</script>
8383

8484
<template>
85-
<div role="table" min-w-max>
86-
<div role="row" class="sticky top-0 z10 border-b border-base" flex="~ row">
87-
<div v-if="selectedFields.includes('hookName')" role="columnheader" bg-base flex-none w32 ws-nowrap p1 text-center font-600>
88-
Hook name
89-
</div>
90-
<div v-if="selectedFields.includes('module')" role="columnheader" bg-base flex-1 min-w100 ws-nowrap p1 text-left font-600>
91-
<button flex="~ row gap1 items-center" w-full>
92-
Module
93-
<VMenu>
85+
<DataVirtualList
86+
v-if="filtered.length && selectedFields.length"
87+
class="plugin-details-table"
88+
role="table"
89+
min-w-max h-full min-h-0
90+
:items="filtered"
91+
key-prop="id"
92+
:page-mode="false"
93+
>
94+
<template #before>
95+
<div role="row" class="border-b border-base bg-base" flex="~ row">
96+
<div v-if="selectedFields.includes('hookName')" role="columnheader" bg-base flex-none w32 ws-nowrap p1 text-center font-600>
97+
Hook name
98+
</div>
99+
<div v-if="selectedFields.includes('module')" role="columnheader" bg-base flex-1 min-w100 ws-nowrap p1 text-left font-600>
100+
<button flex="~ row gap1 items-center" w-full>
101+
Module
102+
<VMenu>
103+
<span w-6 h-6 rounded-full cursor-pointer hover="bg-active" flex="~ items-center justify-center">
104+
<i text-xs class="i-ph-funnel-duotone" :class="filterModuleTypes.length !== searchFilterTypes.length ? 'text-primary op100' : 'op50'" />
105+
</span>
106+
<template #popper>
107+
<div class="p2" flex="~ col gap2">
108+
<label
109+
v-for="rule of searchFilterTypes"
110+
:key="rule.name"
111+
border="~ base rounded-md" px2 py1
112+
flex="~ items-center gap-1"
113+
select-none
114+
:title="rule.description"
115+
class="cursor-pointer module-type-filter"
116+
>
117+
<input
118+
type="checkbox"
119+
mr1
120+
:checked="filterModuleTypes?.includes(rule.name)"
121+
@change="toggleModuleType(rule)"
122+
>
123+
<div :class="rule.icon" icon-catppuccin />
124+
<div text-sm>{{ rule.description || rule.name }}</div>
125+
</label>
126+
</div>
127+
</template>
128+
</VMenu>
129+
</button>
130+
</div>
131+
<div v-if="selectedFields.includes('duration')" role="columnheader" rounded-tr-2 bg-base flex-none ws-nowrap p1 text-center font-600 w-27>
132+
<button flex="~ row gap1 items-center justify-center" w-full @click="toggleDurationSortType">
133+
Duration
94134
<span w-6 h-6 rounded-full cursor-pointer hover="bg-active" flex="~ items-center justify-center">
95-
<i text-xs class="i-ph-funnel-duotone" :class="filterModuleTypes.length !== searchFilterTypes.length ? 'text-primary op100' : 'op50'" />
135+
<i text-xs :class="[durationSortType !== 'asc' ? 'i-ph-arrow-down-duotone' : 'i-ph-arrow-up-duotone', durationSortType ? 'op100 text-primary' : 'op50']" />
96136
</span>
97-
<template #popper>
98-
<div class="p2" flex="~ col gap2">
99-
<label
100-
v-for="rule of searchFilterTypes"
101-
:key="rule.name"
102-
border="~ base rounded-md" px2 py1
103-
flex="~ items-center gap-1"
104-
select-none
105-
:title="rule.description"
106-
class="cursor-pointer module-type-filter"
107-
>
108-
<input
109-
type="checkbox"
110-
mr1
111-
:checked="filterModuleTypes?.includes(rule.name)"
112-
@change="toggleModuleType(rule)"
113-
>
114-
<div :class="rule.icon" icon-catppuccin />
115-
<div text-sm>{{ rule.description || rule.name }}</div>
116-
</label>
117-
</div>
118-
</template>
119-
</VMenu>
120-
</button>
121-
</div>
122-
<div v-if="selectedFields.includes('duration')" role="columnheader" rounded-tr-2 bg-base flex-none ws-nowrap p1 text-center font-600 w-27>
123-
<button flex="~ row gap1 items-center justify-center" w-full @click="toggleDurationSortType">
124-
Duration
125-
<span w-6 h-6 rounded-full cursor-pointer hover="bg-active" flex="~ items-center justify-center">
126-
<i text-xs :class="[durationSortType !== 'asc' ? 'i-ph-arrow-down-duotone' : 'i-ph-arrow-up-duotone', durationSortType ? 'op100 text-primary' : 'op50']" />
127-
</span>
128-
</button>
129-
</div>
130-
<div v-if="selectedFields.includes('startTime')" role="columnheader" rounded-tr-2 bg-base flex-none min-w52 ws-nowrap p1 text-center font-600>
131-
Start Time
132-
</div>
133-
<div v-if="selectedFields.includes('endTime')" role="columnheader" rounded-tr-2 bg-base flex-none min-w52 ws-nowrap p1 text-center font-600>
134-
End Time
137+
</button>
138+
</div>
139+
<div v-if="selectedFields.includes('startTime')" role="columnheader" rounded-tr-2 bg-base flex-none min-w52 ws-nowrap p1 text-center font-600>
140+
Start Time
141+
</div>
142+
<div v-if="selectedFields.includes('endTime')" role="columnheader" rounded-tr-2 bg-base flex-none min-w52 ws-nowrap p1 text-center font-600>
143+
End Time
144+
</div>
135145
</div>
136-
</div>
146+
</template>
137147

138-
<DataVirtualList
139-
v-if="filtered.length && selectedFields.length"
140-
role="rowgroup"
141-
:items="filtered"
142-
key-prop="id"
143-
>
144-
<template #default="{ item, index }">
145-
<div
146-
role="row"
147-
flex="~ row"
148-
class="border-base border-b border-dashed"
149-
:class="[index === filtered.length - 1 ? 'border-b-0' : '']"
150-
>
151-
<div v-if="selectedFields.includes('hookName')" role="cell" flex="~ items-center justify-center" flex-none w32 ws-nowrap op80>
152-
<DisplayBadge :text="HOOK_NAME_MAP[item.type]" />
153-
</div>
154-
<div v-if="selectedFields.includes('module')" role="cell" flex-1 min-w100 text-left text-ellipsis line-clamp-2>
155-
<DisplayModuleId
156-
:id="item.module"
157-
w-full border-none ws-nowrap
158-
:session="session"
159-
:link="`/session/${session.id}/graph?module=${item.module}`"
160-
hover="bg-active"
161-
border="~ base rounded" block px2 py1
162-
/>
163-
</div>
164-
<div v-if="selectedFields.includes('duration')" role="cell" flex="~ items-center justify-center" flex-none text-center text-sm w-27>
165-
<DisplayDuration :duration="item.duration" />
166-
</div>
167-
<div v-if="selectedFields.includes('startTime')" role="cell" flex="~ items-center justify-center" flex-none text-center font-mono text-sm min-w52 op80>
168-
{{ normalizeTimestamp(item.timestamp_start) }}
169-
</div>
170-
<div v-if="selectedFields.includes('endTime')" role="cell" flex="~ items-center justify-center" flex-none text-center font-mono text-sm min-w52 op80>
171-
{{ normalizeTimestamp(item.timestamp_end) }}
172-
</div>
148+
<template #default="{ item, index }">
149+
<div
150+
role="row"
151+
flex="~ row"
152+
class="border-base border-b border-dashed"
153+
:class="[index === filtered.length - 1 ? 'border-b-0' : '']"
154+
>
155+
<div v-if="selectedFields.includes('hookName')" role="cell" flex="~ items-center justify-center" flex-none w32 ws-nowrap op80>
156+
<DisplayBadge :text="HOOK_NAME_MAP[item.type]" />
157+
</div>
158+
<div v-if="selectedFields.includes('module')" role="cell" flex-1 min-w100 text-left text-ellipsis line-clamp-2>
159+
<DisplayModuleId
160+
:id="item.module"
161+
w-full border-none ws-nowrap
162+
:session="session"
163+
:link="`/session/${session.id}/graph?module=${item.module}`"
164+
hover="bg-active"
165+
border="~ base rounded" block px2 py1
166+
/>
167+
</div>
168+
<div v-if="selectedFields.includes('duration')" role="cell" flex="~ items-center justify-center" flex-none text-center text-sm w-27>
169+
<DisplayDuration :duration="item.duration" />
173170
</div>
174-
</template>
175-
</DataVirtualList>
176-
<div v-else>
177-
<div p4>
178-
<div w-full h-48 flex="~ items-center justify-center" op50 italic>
179-
<p v-if="!selectedFields.length">
180-
No columns selected
181-
</p>
182-
<p v-else>
183-
No data
184-
</p>
171+
<div v-if="selectedFields.includes('startTime')" role="cell" flex="~ items-center justify-center" flex-none text-center font-mono text-sm min-w52 op80>
172+
{{ normalizeTimestamp(item.timestamp_start) }}
185173
</div>
174+
<div v-if="selectedFields.includes('endTime')" role="cell" flex="~ items-center justify-center" flex-none text-center font-mono text-sm min-w52 op80>
175+
{{ normalizeTimestamp(item.timestamp_end) }}
176+
</div>
177+
</div>
178+
</template>
179+
</DataVirtualList>
180+
<div v-else role="table" min-w-max h-full>
181+
<div p4>
182+
<div w-full h-48 flex="~ items-center justify-center" op50 italic>
183+
<p v-if="!selectedFields.length">
184+
No columns selected
185+
</p>
186+
<p v-else>
187+
No data
188+
</p>
186189
</div>
187190
</div>
188191
</div>
189192
</template>
193+
194+
<style scoped>
195+
.plugin-details-table:deep(.vue-recycle-scroller__slot) {
196+
position: sticky;
197+
top: 0;
198+
z-index: 10;
199+
}
200+
</style>

packages/rolldown/src/app/components/flowmap/PluginFlow.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ function toggleShowType() {
113113
</template>
114114
</FlowmapPluginFlowTimeline>
115115
</div>
116-
<div flex-1 of-y-auto h-full flex="~ col">
116+
<div flex-1 h-full min-h-0 flex="~ col">
117117
<div flex="~ items-center justify-between" border="b base" px2 h10 bg-base rounded-t-2 of-x-auto ws-nowrap>
118118
<div flex="~ items-center" h-full>
119119
<button v-if="!expanded" w8 h8 rounded-full cursor-pointer mr1 hover="bg-active" flex="~ items-center justify-center" @click="toggleExpanded(true)">
@@ -137,7 +137,7 @@ function toggleShowType() {
137137
</button>
138138
</div>
139139
</div>
140-
<div flex-1 of-y-auto overscroll-contain>
140+
<div flex-1 min-h-0 overscroll-contain>
141141
<DataPluginDetailsTable
142142
:session="session"
143143
:build-metrics="buildMetrics"

packages/ui/src/components/DataVirtualList.vue

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'
33
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
44
5-
defineProps<{
5+
withDefaults(defineProps<{
66
items: T[]
77
keyProp: keyof T
8-
}>()
8+
pageMode?: boolean
9+
}>(), {
10+
pageMode: true,
11+
})
912
1013
defineSlots<{
14+
before?: () => void
1115
default: (props: {
1216
item: T
1317
index: number
@@ -18,23 +22,27 @@ defineSlots<{
1822

1923
<template>
2024
<DynamicScroller
21-
v-slot="{ item, active, index }"
2225
:items="items"
2326
:min-item-size="30"
2427
:key-field="(keyProp as string)"
25-
page-mode
28+
:page-mode="pageMode"
2629
>
27-
<DynamicScrollerItem
28-
:item="(item as T)"
29-
:active="active"
30-
:data-index="index"
31-
>
32-
<slot
33-
v-bind="{ key: (item as T)[keyProp] }"
30+
<template #before>
31+
<slot name="before" />
32+
</template>
33+
<template #default="{ item, active, index }">
34+
<DynamicScrollerItem
3435
:item="(item as T)"
35-
:index="index"
3636
:active="active"
37-
/>
38-
</DynamicScrollerItem>
37+
:data-index="index"
38+
>
39+
<slot
40+
v-bind="{ key: (item as T)[keyProp] }"
41+
:item="(item as T)"
42+
:index="index"
43+
:active="active"
44+
/>
45+
</DynamicScrollerItem>
46+
</template>
3947
</DynamicScroller>
4048
</template>

0 commit comments

Comments
 (0)