metacubexd/src/pages/Rules.tsx

262 lines
8.1 KiB
TypeScript
Raw Normal View History

2023-09-16 01:00:30 +08:00
import { IconReload } from '@tabler/icons-solidjs'
import { createVirtualizer } from '@tanstack/solid-virtual'
2023-09-26 23:40:46 +08:00
import { matchSorter } from 'match-sorter'
import { For, Show, createMemo, createSignal, onMount } from 'solid-js'
2023-09-05 15:38:42 +08:00
import { twMerge } from 'tailwind-merge'
2023-09-16 01:00:30 +08:00
import { Button } from '~/components'
import { useStringBooleanMap } from '~/helpers'
2023-09-22 17:14:35 +08:00
import { useI18n } from '~/i18n'
import { formatTimeFromNow, useRules } from '~/signals'
2023-09-26 23:40:46 +08:00
import { Rule, RuleProvider } from '~/types'
enum ActiveTab {
ruleProviders = 'ruleProviders',
rules = 'rules',
}
2023-08-28 22:13:48 +08:00
export default () => {
const [t] = useI18n()
2023-09-02 17:42:13 +08:00
const {
rules,
ruleProviders,
2023-09-02 17:42:13 +08:00
updateRules,
updateAllRuleProvider,
updateRuleProviderByName,
} = useRules()
2023-08-28 22:13:48 +08:00
onMount(updateRules)
2023-08-28 22:13:48 +08:00
2023-09-05 16:19:14 +08:00
const { map: updatingMap, setWithCallback: setUpdatingMap } =
2023-09-05 15:38:42 +08:00
useStringBooleanMap()
const [allProviderIsUpdating, setAllProviderIsUpdating] = createSignal(false)
const onUpdateProviderClick = (e: MouseEvent, name: string) => {
2023-09-05 15:38:42 +08:00
e.stopPropagation()
2023-09-05 16:19:14 +08:00
void setUpdatingMap(name, () => updateRuleProviderByName(name))
2023-09-02 17:42:13 +08:00
}
2023-08-28 22:13:48 +08:00
2023-09-05 15:38:42 +08:00
const onUpdateAllProviderClick = async (e: MouseEvent) => {
e.stopPropagation()
setAllProviderIsUpdating(true)
try {
await updateAllRuleProvider()
} catch {}
setAllProviderIsUpdating(false)
2023-09-02 17:42:13 +08:00
}
2023-08-28 22:13:48 +08:00
const [activeTab, setActiveTab] = createSignal(ActiveTab.rules)
2023-08-28 22:13:48 +08:00
const tabs = () => [
{
type: ActiveTab.rules,
name: t('rules'),
count: rules().length,
},
{
type: ActiveTab.ruleProviders,
name: t('ruleProviders'),
count: ruleProviders().length,
},
]
2023-08-28 22:13:48 +08:00
const [globalFilter, setGlobalFilter] = createSignal('')
const filteredRules = createMemo(() =>
2023-09-26 23:40:46 +08:00
globalFilter()
? matchSorter(rules(), globalFilter(), {
keys: ['type', 'payload', 'type'] as (keyof Rule)[],
})
: rules(),
)
const filteredRuleProviders = createMemo(() =>
2023-09-26 23:40:46 +08:00
globalFilter()
? matchSorter(ruleProviders(), globalFilter(), {
keys: ['name', 'vehicleType', 'behavior'] as (keyof RuleProvider)[],
})
: ruleProviders(),
)
let scrollElementRef: HTMLDivElement | undefined
const getRuleItemKey = ({ type, payload, proxy }: Rule) =>
`${type}-${payload}-${proxy}`
const ruleVirtualizer = createVirtualizer({
get count() {
return filteredRules().length
},
getItemKey: (index) => getRuleItemKey(filteredRules()[index]),
getScrollElement: () => scrollElementRef!,
estimateSize: () => 82,
overscan: 5,
})
const ruleVirtualizerItems = ruleVirtualizer.getVirtualItems()
const getRuleProviderItemKey = ({
type,
name,
vehicleType,
behavior,
}: RuleProvider) => `${type}-${name}-${vehicleType}-${behavior}`
const ruleProviderVirtualizer = createVirtualizer({
get count() {
return filteredRuleProviders().length
},
getItemKey: (index) =>
getRuleProviderItemKey(filteredRuleProviders()[index]),
getScrollElement: () => scrollElementRef!,
estimateSize: () => 82,
overscan: 5,
})
const ruleProviderVirtualizerItems = ruleProviderVirtualizer.getVirtualItems()
return (
<div class="flex h-full flex-col gap-2">
2023-09-16 00:17:41 +08:00
<div class="flex items-center gap-2">
2023-09-23 14:30:16 +08:00
<div class="tabs-boxed tabs gap-2">
2023-09-16 00:17:41 +08:00
<For each={tabs()}>
{(tab) => (
<button
2023-09-05 20:25:49 +08:00
class={twMerge(
2023-09-16 00:17:41 +08:00
activeTab() === tab.type && 'tab-active',
'tab tab-sm gap-2 px-2 md:tab-md',
2023-09-05 20:25:49 +08:00
)}
2023-09-16 00:17:41 +08:00
onClick={() => setActiveTab(tab.type)}
>
<span>{tab.name}</span>
<div class="badge badge-sm">{tab.count}</div>
</button>
)}
</For>
</div>
2023-09-16 00:17:41 +08:00
<Show when={activeTab() === ActiveTab.ruleProviders}>
<Button
class="btn btn-circle btn-sm"
disabled={allProviderIsUpdating()}
onClick={(e) => onUpdateAllProviderClick(e)}
2023-09-23 14:30:16 +08:00
icon={
<IconReload
class={twMerge(
allProviderIsUpdating() && 'animate-spin text-success',
)}
/>
}
/>
2023-09-16 00:17:41 +08:00
</Show>
</div>
2023-08-28 22:13:48 +08:00
<input
class="input input-bordered input-primary"
placeholder={t('search')}
value={globalFilter()}
onInput={(e) => setGlobalFilter(e.currentTarget.value)}
/>
<div
ref={(ref) => (scrollElementRef = ref)}
class="flex-1 overflow-y-auto"
>
2023-09-16 00:17:41 +08:00
<Show when={activeTab() === ActiveTab.rules}>
<div
class="relative"
style={{ height: `${ruleVirtualizer.getTotalSize()}px` }}
>
{ruleVirtualizerItems.map((virtualizerItem) => {
const rule = filteredRules().find(
(rule) => getRuleItemKey(rule) === virtualizerItem.key,
)!
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>
2023-09-16 00:17:41 +08:00
</div>
</div>
)
})}
2023-09-16 00:17:41 +08:00
</div>
</Show>
<Show when={activeTab() === ActiveTab.ruleProviders}>
<div
class="relative"
style={{ height: `${ruleProviderVirtualizer.getTotalSize()}px` }}
>
{ruleProviderVirtualizerItems.map((virtualizerItem) => {
const ruleProvider = ruleProviders().find(
(ruleProvider) =>
getRuleProviderItemKey(ruleProvider) === virtualizerItem.key,
)!
2023-09-16 00:17:41 +08:00
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>
<div class="text-xs text-slate-500">
{ruleProvider.vehicleType} / {ruleProvider.behavior} /
{t('updated')} {formatTimeFromNow(ruleProvider.updatedAt)}
</div>
2023-09-16 00:17:41 +08:00
<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>
2023-09-02 17:42:13 +08:00
</div>
)
})}
2023-09-16 00:17:41 +08:00
</div>
</Show>
</div>
2023-08-28 22:13:48 +08:00
</div>
)
2023-08-24 04:20:53 +08:00
}