From 20141e791ebb5b9be6ff3faddb8e09f7d58491d8 Mon Sep 17 00:00:00 2001 From: oomeow Date: Wed, 16 Sep 2026 12:23:12 +0000 Subject: [PATCH] fix(profile): virtualize editor lists for rule/proxy/group editors (#7944) * fix(profile): virtualize editor lists for rule/proxy/group editors * refactor(list): remove feedbackClone from sortable item and list integration * refactor(profile): use type instead of export type for category * refactor(ui): rename measureElementRef to measureElement in sortable components --- src/components/base/sortable-item.tsx | 19 +- .../profile/grouped-virtual-list.tsx | 184 +++++++++++++ .../profile/groups-editor-viewer.tsx | 241 +++++----------- .../profile/proxies-editor-viewer.tsx | 237 +++++----------- .../profile/rules-editor-viewer.tsx | 260 ++++++------------ 5 files changed, 425 insertions(+), 516 deletions(-) create mode 100644 src/components/profile/grouped-virtual-list.tsx diff --git a/src/components/base/sortable-item.tsx b/src/components/base/sortable-item.tsx index 0fb923ba9..8af365e85 100644 --- a/src/components/base/sortable-item.tsx +++ b/src/components/base/sortable-item.tsx @@ -13,12 +13,24 @@ interface SortableItemProps { index: number group?: string disabled?: boolean + // 用于给 tanstack virtual 列表测量当前拖动元素尺寸 + measureElement?: (element: HTMLDivElement | null) => void + dataIndex?: number style?: CSSProperties children: ReactNode | ((props: SortableItemRenderProps) => ReactNode) } export const SortableItem = (props: SortableItemProps) => { - const { id, index, group, disabled, children, style } = props + const { + id, + index, + group, + disabled, + measureElement, + dataIndex, + children, + style, + } = props const { ref, handleRef, sortable, isDragging } = useSortable({ id, index, @@ -30,8 +42,9 @@ export const SortableItem = (props: SortableItemProps) => { (element: HTMLDivElement | null) => { handleRef(element?.querySelector('[data-sortable-handle]') ?? null) ref(element) + measureElement?.(element) }, - [handleRef, ref], + [handleRef, ref, measureElement], ) const mergedStyle: CSSProperties = { @@ -53,7 +66,7 @@ export const SortableItem = (props: SortableItemProps) => { } return ( -
+
{children}
) diff --git a/src/components/profile/grouped-virtual-list.tsx b/src/components/profile/grouped-virtual-list.tsx new file mode 100644 index 000000000..07b657f37 --- /dev/null +++ b/src/components/profile/grouped-virtual-list.tsx @@ -0,0 +1,184 @@ +import { move } from '@dnd-kit/helpers' +import { + DragDropProvider, + type DragEndEvent, + type DragOverEvent, + KeyboardSensor, + PointerSensor, +} from '@dnd-kit/react' +import { isSortable, isSortableOperation } from '@dnd-kit/react/sortable' +import { useVirtualizer } from '@tanstack/react-virtual' +import { type CSSProperties, type ReactNode, useRef, useState } from 'react' + +import { SortableItem } from '../base' + +type GroupedVirtualCategory = 'prepend' | 'original' | 'append' + +export interface GroupedVirtualItem { + id: string + category: GroupedVirtualCategory + item: T +} + +// eslint-disable-next-line react-refresh/only-export-components +export function buildGroupedItems( + prepend: T[], + original: T[], + append: T[], + getKey: (item: T) => string, +): GroupedVirtualItem[] { + const build = ( + category: GroupedVirtualCategory, + list: T[], + ): GroupedVirtualItem[] => + list.map((item) => ({ + id: `${category}:${getKey(item)}`, + category, + item, + })) + + return [ + ...build('prepend', prepend), + ...build('original', original), + ...build('append', append), + ] +} + +interface GroupedVirtualListProps { + items: GroupedVirtualItem[] + estimateSize?: number + renderItem: (entry: GroupedVirtualItem) => ReactNode + onReorder: ( + category: 'prepend' | 'append', + activeIndex: number, + overIndex: number, + ) => void + style?: CSSProperties +} + +/** + * 用于编辑规则/节点/代理组功能的 prepend/original/append 分组虚拟化列表 + */ +export function GroupedVirtualList(props: GroupedVirtualListProps) { + const { + items: itemsProp, + estimateSize = 56, + renderItem, + onReorder, + style, + } = props + const [items, setItems] = useState(itemsProp) + const [prevItemsProp, setPrevItemsProp] = useState(itemsProp) + const snapshotRef = useRef(itemsProp) + const scrollRef = useRef(null) + + if (itemsProp !== prevItemsProp) { + setPrevItemsProp(itemsProp) + setItems(itemsProp) + } + + const virtualizer = useVirtualizer({ + count: items.length, + getScrollElement: () => scrollRef.current, + estimateSize: () => estimateSize, + overscan: 15, + gap: 8, + getItemKey: (index) => items[index]?.id ?? index, + }) + + const onDragStart = () => { + snapshotRef.current = items + } + + const onDragOver = (event: DragOverEvent) => { + const { operation } = event + if (!isSortableOperation(operation)) return + + const { source, target } = operation + if (!source || !target) return + + const sourceEntry = items.find((entry) => entry.id === source.id) + const targetEntry = items.find((entry) => entry.id === target.id) + if ( + !sourceEntry || + !targetEntry || + sourceEntry.category !== targetEntry.category + ) { + event.preventDefault() + return + } + + setItems((current) => move(current, event)) + } + + const onDragEnd = (event: DragEndEvent) => { + const source = event.operation.source + + if (event.canceled) { + setItems(snapshotRef.current) + return + } + + if (!isSortable(source)) return + + const snapshot = snapshotRef.current + const sourceEntry = snapshot.find((entry) => entry.id === source.id) + if (!sourceEntry) return + + const { category } = sourceEntry + if (category !== 'prepend' && category !== 'append') return + + const activeIndex = snapshot + .filter((entry) => entry.category === category) + .findIndex((entry) => entry.id === source.id) + const overIndex = items + .filter((entry) => entry.category === category) + .findIndex((entry) => entry.id === source.id) + if (activeIndex < 0 || overIndex < 0 || activeIndex === overIndex) return + + onReorder(category, activeIndex, overIndex) + } + + return ( + +
+
+ {virtualizer.getVirtualItems().map((vi) => { + const entry = items[vi.index] + if (!entry) return null + + return ( + + {renderItem(entry)} + + ) + })} +
+
+
+ ) +} diff --git a/src/components/profile/groups-editor-viewer.tsx b/src/components/profile/groups-editor-viewer.tsx index 93749658b..66fc3d836 100644 --- a/src/components/profile/groups-editor-viewer.tsx +++ b/src/components/profile/groups-editor-viewer.tsx @@ -1,12 +1,4 @@ import { arrayMove } from '@dnd-kit/helpers' -import { - DragDropProvider, - type DragOverEvent, - KeyboardSensor, - PointerSensor, - type DragEndEvent, -} from '@dnd-kit/react' -import { isSortable, isSortableOperation } from '@dnd-kit/react/sortable' import { VerticalAlignBottomRounded, VerticalAlignTopRounded, @@ -43,13 +35,7 @@ import { import { Controller, useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' -import { - BaseSearchBox, - MonacoEditor, - SortableItem, - Switch, - VirtualList, -} from '@/components/base' +import { BaseSearchBox, MonacoEditor, Switch } from '@/components/base' import { GroupItem } from '@/components/profile/group-item' import { getNetworkInterfaces, @@ -63,6 +49,12 @@ import type { MonacoEditorInstance } from '@/types/monaco' import { MONACO_FONT_FAMILY } from '@/utils/font-family' import { parseYamlSafe } from '@/utils/yaml' +import { + buildGroupedItems, + type GroupedVirtualItem, + GroupedVirtualList, +} from './grouped-virtual-list' + interface Props { proxiesUid: string mergeUid: string @@ -203,106 +195,71 @@ export const GroupsEditorViewer = (props: Props) => { [appendSeq, match], ) - const renderItem = (index: number): React.ReactNode => { - const shift = filteredPrependSeq.length > 0 ? 1 : 0 - if (filteredPrependSeq.length > 0 && index === 0) { + const items = useMemo( + () => + buildGroupedItems( + filteredPrependSeq, + filteredGroupList, + filteredAppendSeq, + (group) => group.name, + ), + [filteredPrependSeq, filteredGroupList, filteredAppendSeq], + ) + + const renderItem = (entry: GroupedVirtualItem) => { + const { category, item } = entry + + if (category === 'original') { + const isDeleted = deleteSeq.includes(item.name) return ( - <> - {filteredPrependSeq.map((item, itemIndex) => { - return ( - - { - setPrependSeq( - prependSeq.filter((v) => v.name !== item.name), - ) - }} - /> - - ) - })} - - ) - } else if (index < filteredGroupList.length + shift) { - const newIndex = index - shift - return ( - - { + if (isDeleted) { + setDeleteSeq(deleteSeq.filter((v) => v !== item.name)) + } else { + setDeleteSeq((prev) => [...prev, item.name]) } - group={filteredGroupList[newIndex]} - onDelete={() => { - if (deleteSeq.includes(filteredGroupList[newIndex].name)) { - setDeleteSeq( - deleteSeq.filter( - (v) => v !== filteredGroupList[newIndex].name, - ), - ) - } else { - setDeleteSeq((prev) => [ - ...prev, - filteredGroupList[newIndex].name, - ]) - } - }} - /> - + }} + /> ) - } else { + } + + if (category === 'prepend') { return ( - <> - {filteredAppendSeq.map((item, itemIndex) => { - return ( - - { - setAppendSeq(appendSeq.filter((v) => v.name !== item.name)) - }} - /> - - ) - })} - + { + setPrependSeq(prependSeq.filter((v) => v.name !== item.name)) + }} + /> ) } + + return ( + { + setAppendSeq(appendSeq.filter((v) => v.name !== item.name)) + }} + /> + ) } - const onPrependDragEnd = async (event: DragEndEvent) => { - const { operation, canceled } = event - const { source, target } = operation - if (canceled || !target || !isSortable(source)) return - - const { index: overIndex, initialIndex: activeIndex } = source.sortable - const activeRealIndex = findRealIndex( - prependSeq, - filteredPrependSeq, - activeIndex, - ) - const overRealIndex = findRealIndex( - prependSeq, - filteredPrependSeq, - overIndex, - ) + const onReorder = ( + category: 'prepend' | 'append', + activeIndex: number, + overIndex: number, + ) => { + const list = category === 'prepend' ? prependSeq : appendSeq + const filtered = + category === 'prepend' ? filteredPrependSeq : filteredAppendSeq + const setList = category === 'prepend' ? setPrependSeq : setAppendSeq + const activeRealIndex = findRealIndex(list, filtered, activeIndex) + const overRealIndex = findRealIndex(list, filtered, overIndex) if ( activeRealIndex < 0 || overRealIndex < 0 || @@ -311,55 +268,9 @@ export const GroupsEditorViewer = (props: Props) => { return } - setPrependSeq(arrayMove(prependSeq, activeRealIndex, overRealIndex)) - } - const onAppendDragEnd = async (event: DragEndEvent) => { - const { operation, canceled } = event - const { source, target } = operation - if (canceled || !target || !isSortable(source)) return - - const { index: overIndex, initialIndex: activeIndex } = source.sortable - const activeRealIndex = findRealIndex( - appendSeq, - filteredAppendSeq, - activeIndex, - ) - const overRealIndex = findRealIndex(appendSeq, filteredAppendSeq, overIndex) - if ( - activeRealIndex < 0 || - overRealIndex < 0 || - activeRealIndex === overRealIndex - ) { - return - } - - setAppendSeq(arrayMove(appendSeq, activeRealIndex, overRealIndex)) + setList(arrayMove(list, activeRealIndex, overRealIndex)) } - // prevent drag over if source and target are in different groups - const onDragOver = (event: DragOverEvent) => { - const { operation } = event - if (!isSortableOperation(operation)) return - - const { source, target } = operation - if (source?.group !== target?.group) { - event.preventDefault() - } - } - - const onDragEnd = async (event: DragEndEvent) => { - const source = event.operation.source - if (!isSortable(source)) return - - const { initialGroup, group } = source.sortable - if (initialGroup !== group) return - - if (group === 'prepend') { - await onPrependDragEnd(event) - } else if (group === 'append') { - await onAppendDragEnd(event) - } - } const fetchContent = useCallback(async () => { hasLoadedSeqConfigRef.current = false const data = await readProfileFile(property) @@ -1152,22 +1063,12 @@ export const GroupsEditorViewer = (props: Props) => { }} > setMatch(() => match)} /> - - 0 ? 1 : 0) + - (filteredAppendSeq.length > 0 ? 1 : 0) - } - estimateSize={56} - renderItem={renderItem} - style={{ height: 'calc(100% - 24px)', marginTop: '8px' }} - /> - + ) : ( diff --git a/src/components/profile/proxies-editor-viewer.tsx b/src/components/profile/proxies-editor-viewer.tsx index 79f2895c5..9d3666752 100644 --- a/src/components/profile/proxies-editor-viewer.tsx +++ b/src/components/profile/proxies-editor-viewer.tsx @@ -1,12 +1,4 @@ import { arrayMove } from '@dnd-kit/helpers' -import { - DragDropProvider, - type DragOverEvent, - KeyboardSensor, - PointerSensor, - type DragEndEvent, -} from '@dnd-kit/react' -import { isSortable, isSortableOperation } from '@dnd-kit/react/sortable' import { VerticalAlignBottomRounded, VerticalAlignTopRounded, @@ -35,12 +27,7 @@ import { } from 'react' import { useTranslation } from 'react-i18next' -import { - BaseSearchBox, - MonacoEditor, - SortableItem, - VirtualList, -} from '@/components/base' +import { BaseSearchBox, MonacoEditor } from '@/components/base' import { ProxyItem } from '@/components/profile/proxy-item' import { readProfileFile, saveProfileFile } from '@/services/cmds' import { showNotice } from '@/services/notice-service' @@ -50,6 +37,12 @@ import { MONACO_FONT_FAMILY } from '@/utils/font-family' import parseUri from '@/utils/uri-parser' import { parseYamlSafe } from '@/utils/yaml' +import { + buildGroupedItems, + type GroupedVirtualItem, + GroupedVirtualList, +} from './grouped-virtual-list' + interface Props { profileUid: string property: string @@ -107,106 +100,71 @@ export const ProxiesEditorViewer = (props: Props) => { [appendSeq, match], ) - const renderItem = (index: number): React.ReactNode => { - const shift = filteredPrependSeq.length > 0 ? 1 : 0 - if (filteredPrependSeq.length > 0 && index === 0) { + const items = useMemo( + () => + buildGroupedItems( + filteredPrependSeq, + filteredProxyList, + filteredAppendSeq, + (proxy) => proxy.name, + ), + [filteredPrependSeq, filteredProxyList, filteredAppendSeq], + ) + + const renderItem = (entry: GroupedVirtualItem) => { + const { category, item } = entry + + if (category === 'original') { + const isDeleted = deleteSeq.includes(item.name) return ( - <> - {filteredPrependSeq.map((item, itemIndex) => { - return ( - - { - setPrependSeq( - prependSeq.filter((v) => v.name !== item.name), - ) - }} - /> - - ) - })} - - ) - } else if (index < filteredProxyList.length + shift) { - const newIndex = index - shift - return ( - - { + if (isDeleted) { + setDeleteSeq(deleteSeq.filter((v) => v !== item.name)) + } else { + setDeleteSeq((prev) => [...prev, item.name]) } - proxy={filteredProxyList[newIndex]} - onDelete={() => { - if (deleteSeq.includes(filteredProxyList[newIndex].name)) { - setDeleteSeq( - deleteSeq.filter( - (v) => v !== filteredProxyList[newIndex].name, - ), - ) - } else { - setDeleteSeq((prev) => [ - ...prev, - filteredProxyList[newIndex].name, - ]) - } - }} - /> - + }} + /> ) - } else { + } + + if (category === 'prepend') { return ( - <> - {filteredAppendSeq.map((item, itemIndex) => { - return ( - - { - setAppendSeq(appendSeq.filter((v) => v.name !== item.name)) - }} - /> - - ) - })} - + { + setPrependSeq(prependSeq.filter((v) => v.name !== item.name)) + }} + /> ) } + + return ( + { + setAppendSeq(appendSeq.filter((v) => v.name !== item.name)) + }} + /> + ) } - const onPrependDragEnd = async (event: DragEndEvent) => { - const { operation, canceled } = event - const { source, target } = operation - if (canceled || !target || !isSortable(source)) return - - const { index: overIndex, initialIndex: activeIndex } = source.sortable - const activeRealIndex = findRealIndex( - prependSeq, - filteredPrependSeq, - activeIndex, - ) - const overRealIndex = findRealIndex( - prependSeq, - filteredPrependSeq, - overIndex, - ) + const onReorder = ( + category: 'prepend' | 'append', + activeIndex: number, + overIndex: number, + ) => { + const list = category === 'prepend' ? prependSeq : appendSeq + const filtered = + category === 'prepend' ? filteredPrependSeq : filteredAppendSeq + const setList = category === 'prepend' ? setPrependSeq : setAppendSeq + const activeRealIndex = findRealIndex(list, filtered, activeIndex) + const overRealIndex = findRealIndex(list, filtered, overIndex) if ( activeRealIndex < 0 || overRealIndex < 0 || @@ -215,52 +173,9 @@ export const ProxiesEditorViewer = (props: Props) => { return } - setPrependSeq(arrayMove(prependSeq, activeRealIndex, overRealIndex)) + setList(arrayMove(list, activeRealIndex, overRealIndex)) } - const onAppendDragEnd = async (event: DragEndEvent) => { - const { operation, canceled } = event - const { source, target } = operation - if (canceled || !target || !isSortable(source)) return - const { index: overIndex, initialIndex: activeIndex } = source.sortable - const activeRealIndex = findRealIndex( - appendSeq, - filteredAppendSeq, - activeIndex, - ) - const overRealIndex = findRealIndex(appendSeq, filteredAppendSeq, overIndex) - if ( - activeRealIndex < 0 || - overRealIndex < 0 || - activeRealIndex === overRealIndex - ) { - return - } - - setAppendSeq(arrayMove(appendSeq, activeRealIndex, overRealIndex)) - } - const onDragOver = (event: DragOverEvent) => { - const { operation } = event - if (!isSortableOperation(operation)) return - - const { source, target } = operation - if (source?.group !== target?.group) { - event.preventDefault() - } - } - const onDragEnd = async (event: DragEndEvent) => { - const source = event.operation.source - if (!isSortable(source)) return - - const { initialGroup, group } = source.sortable - if (initialGroup !== group) return - - if (group === 'prepend') { - await onPrependDragEnd(event) - } else if (group === 'append') { - await onAppendDragEnd(event) - } - } // 优化:异步分片解析,避免主线程阻塞,解析完成后批量setState const handleParseAsync = (cb: (proxies: IProxyConfig[]) => void) => { const proxies: IProxyConfig[] = [] @@ -522,22 +437,12 @@ export const ProxiesEditorViewer = (props: Props) => { }} > setMatch(() => match)} /> - - 0 ? 1 : 0) + - (filteredAppendSeq.length > 0 ? 1 : 0) - } - estimateSize={56} - renderItem={renderItem} - style={{ height: 'calc(100% - 24px)', marginTop: '8px' }} - /> - + ) : ( diff --git a/src/components/profile/rules-editor-viewer.tsx b/src/components/profile/rules-editor-viewer.tsx index 483ace9a1..eccb59730 100644 --- a/src/components/profile/rules-editor-viewer.tsx +++ b/src/components/profile/rules-editor-viewer.tsx @@ -1,11 +1,4 @@ -import { - DragDropProvider, - type DragOverEvent, - KeyboardSensor, - PointerSensor, - type DragEndEvent, -} from '@dnd-kit/react' -import { isSortable, isSortableOperation } from '@dnd-kit/react/sortable' +import { arrayMove } from '@dnd-kit/helpers' import { VerticalAlignBottomRounded, VerticalAlignTopRounded, @@ -36,13 +29,7 @@ import { } from 'react' import { useTranslation } from 'react-i18next' -import { - BaseSearchBox, - MonacoEditor, - SortableItem, - Switch, - VirtualList, -} from '@/components/base' +import { BaseSearchBox, MonacoEditor, Switch } from '@/components/base' import { RuleItem } from '@/components/profile/rule-item' import { readProfileFile, saveProfileFile } from '@/services/cmds' import { showNotice } from '@/services/notice-service' @@ -54,6 +41,12 @@ import getSystem from '@/utils/get-system' import { isValidIpCidr } from '@/utils/network' import { parseYamlSafe } from '@/utils/yaml' +import { + buildGroupedItems, + type GroupedVirtualItem, + GroupedVirtualList, +} from './grouped-virtual-list' + interface Props { groupsUid: string mergeUid: string @@ -302,117 +295,83 @@ export const RulesEditorViewer = (props: Props) => { [appendSeq, match], ) - const renderItem = (index: number): React.ReactNode => { - const shift = filteredPrependSeq.length > 0 ? 1 : 0 - if (filteredPrependSeq.length > 0 && index === 0) { + const items = useMemo( + () => + buildGroupedItems( + filteredPrependSeq, + filteredRuleList, + filteredAppendSeq, + (rule) => rule, + ), + [filteredPrependSeq, filteredRuleList, filteredAppendSeq], + ) + + const renderItem = (entry: GroupedVirtualItem) => { + const { category, item } = entry + + if (category === 'original') { + const isDeleted = deleteSeq.includes(item) return ( - <> - {filteredPrependSeq.map((item, itemIndex) => { - return ( - - { - setPrependSeq(prependSeq.filter((v) => v !== item)) - }} - onAppend={() => { - setAppendSeq((prev) => - prev.includes(item) ? prev : [...prev, item], - ) - setPrependSeq(prependSeq.filter((v) => v !== item)) - }} - /> - - ) - })} - - ) - } else if (index < filteredRuleList.length + shift) { - const newIndex = index - shift - return ( - - { + if (isDeleted) { + setDeleteSeq(deleteSeq.filter((v) => v !== item)) + } else { + setDeleteSeq((prev) => [...prev, item]) } - ruleRaw={filteredRuleList[newIndex]} - onDelete={() => { - if (deleteSeq.includes(filteredRuleList[newIndex])) { - setDeleteSeq( - deleteSeq.filter((v) => v !== filteredRuleList[newIndex]), - ) - } else { - setDeleteSeq((prev) => [...prev, filteredRuleList[newIndex]]) - } - }} - /> - + }} + /> ) - } else { + } + + if (category === 'prepend') { return ( - <> - {filteredAppendSeq.map((item, itemIndex) => { - return ( - - { - setAppendSeq(appendSeq.filter((v) => v !== item)) - }} - onPrepend={() => { - setPrependSeq((prev) => - prev.includes(item) ? prev : [...prev, item], - ) - setAppendSeq(appendSeq.filter((v) => v !== item)) - }} - /> - + { + setPrependSeq(prependSeq.filter((v) => v !== item)) + }} + onAppend={() => { + setAppendSeq((prev) => + prev.includes(item) ? prev : [...prev, item], ) - })} - + setPrependSeq(prependSeq.filter((v) => v !== item)) + }} + /> ) } + + return ( + { + setAppendSeq(appendSeq.filter((v) => v !== item)) + }} + onPrepend={() => { + setPrependSeq((prev) => + prev.includes(item) ? prev : [...prev, item], + ) + setAppendSeq(appendSeq.filter((v) => v !== item)) + }} + /> + ) } - const reorder = (list: string[], startIndex: number, endIndex: number) => { - const result = Array.from(list) - const [removed] = result.splice(startIndex, 1) - result.splice(endIndex, 0, removed) - return result - } - const onPrependDragEnd = async (event: DragEndEvent) => { - const { operation, canceled } = event - const { source, target } = operation - if (canceled || !target || !isSortable(source)) return - - const { index: overIndex, initialIndex: activeIndex } = source.sortable - const activeRealIndex = findRealIndex( - prependSeq, - filteredPrependSeq, - activeIndex, - ) - const overRealIndex = findRealIndex( - prependSeq, - filteredPrependSeq, - overIndex, - ) + const onReorder = ( + category: 'prepend' | 'append', + activeIndex: number, + overIndex: number, + ) => { + const list = category === 'prepend' ? prependSeq : appendSeq + const filtered = + category === 'prepend' ? filteredPrependSeq : filteredAppendSeq + const setList = category === 'prepend' ? setPrependSeq : setAppendSeq + const activeRealIndex = findRealIndex(list, filtered, activeIndex) + const overRealIndex = findRealIndex(list, filtered, overIndex) if ( activeRealIndex < 0 || overRealIndex < 0 || @@ -421,52 +380,9 @@ export const RulesEditorViewer = (props: Props) => { return } - setPrependSeq(reorder(prependSeq, activeRealIndex, overRealIndex)) + setList(arrayMove(list, activeRealIndex, overRealIndex)) } - const onAppendDragEnd = async (event: DragEndEvent) => { - const { operation, canceled } = event - const { source, target } = operation - if (canceled || !target || !isSortable(source)) return - const { index: overIndex, initialIndex: activeIndex } = source.sortable - const activeRealIndex = findRealIndex( - appendSeq, - filteredAppendSeq, - activeIndex, - ) - const overRealIndex = findRealIndex(appendSeq, filteredAppendSeq, overIndex) - if ( - activeRealIndex < 0 || - overRealIndex < 0 || - activeRealIndex === overRealIndex - ) { - return - } - - setAppendSeq(reorder(appendSeq, activeRealIndex, overRealIndex)) - } - const onDragOver = (event: DragOverEvent) => { - const { operation } = event - if (!isSortableOperation(operation)) return - - const { source, target } = operation - if (source?.group !== target?.group) { - event.preventDefault() - } - } - const onDragEnd = async (event: DragEndEvent) => { - const source = event.operation.source - if (!isSortable(source)) return - - const { initialGroup, group } = source.sortable - if (initialGroup !== group) return - - if (group === 'prepend') { - await onPrependDragEnd(event) - } else if (group === 'append') { - await onAppendDragEnd(event) - } - } const fetchContent = useCallback(async () => { hasLoadedSeqConfigRef.current = false const data = await readProfileFile(property) @@ -854,22 +770,12 @@ export const RulesEditorViewer = (props: Props) => { }} > setMatch(() => match)} /> - - 0 ? 1 : 0) + - (filteredAppendSeq.length > 0 ? 1 : 0) - } - estimateSize={56} - renderItem={renderItem} - style={{ height: 'calc(100% - 24px)', marginTop: '8px' }} - /> - + ) : (