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", "@tabler/icons-solidjs": "^2.35.0",
"@tanstack/match-sorter-utils": "^8.8.4", "@tanstack/match-sorter-utils": "^8.8.4",
"@tanstack/solid-table": "^8.10.3", "@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", "@thisbeyond/solid-dnd": "^0.7.4",
"@types/byte-size": "^8.1.0", "@types/byte-size": "^8.1.0",
"@types/lodash": "^4.14.199", "@types/lodash": "^4.14.199",

View File

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

View File

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