feat(rule): implement virtual scroll on rules and rule providers, fixes #285

This commit is contained in:
kunish 2023-09-26 22:22:24 +08:00
parent f8bf02e9c7
commit 7145cec62f
No known key found for this signature in database
GPG Key ID: 647A12B4F782C430
3 changed files with 117 additions and 55 deletions

View File

@ -32,7 +32,8 @@
"@tabler/icons-solidjs": "^2.35.0",
"@tanstack/match-sorter-utils": "^8.8.4",
"@tanstack/solid-table": "^8.10.3",
"@tanstack/solid-virtual": "3.0.0-beta.6",
"@tanstack/solid-virtual": "3.0.0-beta.60",
"@tanstack/virtual-core": "3.0.0-beta.60",
"@thisbeyond/solid-dnd": "^0.7.4",
"@types/byte-size": "^8.1.0",
"@types/lodash": "^4.14.199",

View File

@ -60,8 +60,11 @@ dependencies:
specifier: ^8.10.3
version: 8.10.3(solid-js@1.7.12)
'@tanstack/solid-virtual':
specifier: 3.0.0-beta.6
version: 3.0.0-beta.6
specifier: 3.0.0-beta.60
version: 3.0.0-beta.60(solid-js@1.7.12)
'@tanstack/virtual-core':
specifier: 3.0.0-beta.60
version: 3.0.0-beta.60
'@thisbeyond/solid-dnd':
specifier: ^0.7.4
version: 0.7.4(solid-js@1.7.12)
@ -1968,10 +1971,6 @@ packages:
tslib: 2.6.2
dev: false
/@reach/observe-rect@1.2.0:
resolution: {integrity: sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==}
dev: false
/@rollup/plugin-babel@5.3.1(@babel/core@7.22.20)(rollup@2.79.1):
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
engines: {node: '>= 10.0.0'}
@ -2194,11 +2193,13 @@ packages:
solid-js: 1.7.12
dev: false
/@tanstack/solid-virtual@3.0.0-beta.6:
resolution: {integrity: sha512-/HjeHZb4UZxxFSAkICUEWOozGwHQpKlvtnUoS5uSMSuLOz0XM5vFq6zR6ENwAczKWDtkh8ntddk+zXAhyXOlEw==}
engines: {node: '>=12'}
/@tanstack/solid-virtual@3.0.0-beta.60(solid-js@1.7.12):
resolution: {integrity: sha512-SN007vDq1AeRq5w2rK0E47CeI8rv+oPZ5IjtOgLynt1MKNUsSsJ7d+igK2jUPQ24QrZf5NLrN4FRQRdxvakvIg==}
peerDependencies:
solid-js: ^1.3.0
dependencies:
'@reach/observe-rect': 1.2.0
'@tanstack/virtual-core': 3.0.0-beta.60
solid-js: 1.7.12
dev: false
/@tanstack/table-core@8.10.3:
@ -2206,6 +2207,10 @@ packages:
engines: {node: '>=12'}
dev: false
/@tanstack/virtual-core@3.0.0-beta.60:
resolution: {integrity: sha512-QlCdhsV1+JIf0c0U6ge6SQmpwsyAT0oQaOSZk50AtEeAyQl9tQrd6qCHAslxQpgphrfe945abvKG8uYvw3hIGA==}
dev: false
/@thisbeyond/solid-dnd@0.7.4(solid-js@1.7.12):
resolution: {integrity: sha512-jgV9EtR3gAtVsILG8p1OAGrhHIgnK4W04YxpyLgJRCDKEFYQWuDrMdUe8F5Kc6pcVXlC4IMXr4cB8fS2Ut3/Ow==}
peerDependencies:

View File

@ -1,4 +1,5 @@
import { IconReload } from '@tabler/icons-solidjs'
import { createVirtualizer } from '@tanstack/solid-virtual'
import { For, Show, createSignal, onMount } from 'solid-js'
import { twMerge } from 'tailwind-merge'
import { Button } from '~/components'
@ -56,6 +57,30 @@ export default () => {
},
]
let parentRef: HTMLDivElement | undefined
const ruleVirtualizer = createVirtualizer({
get count() {
return rules().length
},
getScrollElement: () => parentRef!,
estimateSize: () => 74,
overscan: 5,
})
const ruleVirtualizerItems = ruleVirtualizer.getVirtualItems()
const ruleProviderVirtualizer = createVirtualizer({
get count() {
return ruleProviders().length
},
getScrollElement: () => parentRef!,
estimateSize: () => 74,
overscan: 5,
})
const ruleProviderVirtualizerItems = ruleProviderVirtualizer.getVirtualItems()
return (
<div class="flex h-full flex-col gap-2">
<div class="flex items-center gap-2">
@ -92,60 +117,91 @@ export default () => {
</Show>
</div>
<div class="flex-1 overflow-y-auto">
<div ref={(ref) => (parentRef = ref)} class="flex-1 overflow-y-auto">
<Show when={activeTab() === ActiveTab.rules}>
<div class="grid gap-2">
<For each={rules()}>
{(rule) => (
<div class="card card-bordered card-compact bg-base-200 p-4">
<div class="flex items-center gap-2">
<span class="break-all">{rule.payload}</span>
<Show
when={typeof rule.size === 'number' && rule.size !== -1}
>
<div class="badge badge-sm">{rule.size}</div>
</Show>
</div>
<div class="text-xs text-slate-500">
{rule.type} :: {rule.proxy}
<div
class="relative"
style={{ height: `${ruleVirtualizer.getTotalSize()}px` }}
>
{ruleVirtualizerItems.map((virtualizerItem) => {
const rule = rules()[virtualizerItem.index]
return (
<div
ref={(el) =>
onMount(() => ruleVirtualizer.measureElement(el))
}
data-index={virtualizerItem.index}
class="absolute inset-x-0 top-0 pb-2 last:pb-0"
style={{
transform: `translateY(${virtualizerItem.start}px)`,
}}
>
<div class="card card-bordered card-compact bg-base-200 p-4">
<div class="flex items-center gap-2">
<span class="break-all">{rule.payload}</span>
<Show when={rule.size !== -1}>
<div class="badge badge-sm">{rule.size}</div>
</Show>
</div>
<div class="text-xs text-slate-500">
{rule.type} :: {rule.proxy}
</div>
</div>
</div>
)}
</For>
)
})}
</div>
</Show>
<Show when={activeTab() === ActiveTab.ruleProviders}>
<div class="grid gap-2">
<For each={ruleProviders()}>
{(ruleProvider) => (
<div class="card card-bordered card-compact bg-base-200 p-4">
<div class="flex items-center gap-2 pr-8">
<span class="break-all">{ruleProvider.name}</span>
<div class="badge badge-sm">{ruleProvider.ruleCount}</div>
</div>
<div class="relative">
{ruleProviderVirtualizerItems.map((virtualizerItem) => {
const ruleProvider = ruleProviders()[virtualizerItem.index]
<div class="text-xs text-slate-500">
{ruleProvider.vehicleType} / {ruleProvider.behavior} /
{t('updated')} {formatTimeFromNow(ruleProvider.updatedAt)}
</div>
return (
<div
ref={(el) =>
onMount(() => ruleProviderVirtualizer.measureElement(el))
}
data-index={virtualizerItem.index}
class="absolute inset-x-0 top-0 pb-2 last:pb-0"
style={{
transform: `translateY(${virtualizerItem.start}px)`,
}}
>
<div class="card card-bordered card-compact bg-base-200 p-4">
<div class="flex items-center gap-2 pr-8">
<span class="break-all">{ruleProvider.name}</span>
<div class="badge badge-sm">{ruleProvider.ruleCount}</div>
</div>
<Button
class="btn-circle btn-sm absolute right-2 top-2 mr-2 h-4"
disabled={updatingMap()[ruleProvider.name]}
onClick={(e) => onUpdateProviderClick(e, ruleProvider.name)}
icon={
<IconReload
class={twMerge(
updatingMap()[ruleProvider.name] &&
'animate-spin text-success',
)}
/>
}
/>
<div class="text-xs text-slate-500">
{ruleProvider.vehicleType} / {ruleProvider.behavior} /
{t('updated')} {formatTimeFromNow(ruleProvider.updatedAt)}
</div>
<Button
class="btn-circle btn-sm absolute right-2 top-2 mr-2 h-4"
disabled={updatingMap()[ruleProvider.name]}
onClick={(e) =>
onUpdateProviderClick(e, ruleProvider.name)
}
icon={
<IconReload
class={twMerge(
updatingMap()[ruleProvider.name] &&
'animate-spin text-success',
)}
/>
}
/>
</div>
</div>
)}
</For>
)
})}
</div>
</Show>
</div>