mixvideo-v2/apps/desktop/src/components/VirtualizedSegmentList.tsx

129 lines
4.1 KiB
TypeScript

import React, { useMemo, useCallback } from 'react';
import { VariableSizeList as List } from 'react-window';
import {
ClassificationGroup,
ModelGroup,
MaterialSegmentViewMode
} from '../types/materialSegmentView';
import { MaterialSegmentGroup } from './MaterialSegmentGroup';
interface VirtualizedSegmentListProps {
groups: (ClassificationGroup | ModelGroup)[];
viewMode: MaterialSegmentViewMode;
selectedSegmentIds: Set<string>;
expandedGroups: Set<string>;
onToggleExpand: (groupId: string) => void;
onSelectSegment: (segmentId: string) => void;
height: number;
}
/**
* 虚拟化MaterialSegment列表组件
* 遵循 Tauri 开发规范的性能优化组件设计模式
*/
export const VirtualizedSegmentList: React.FC<VirtualizedSegmentListProps> = ({
groups,
viewMode,
selectedSegmentIds,
expandedGroups,
onToggleExpand,
onSelectSegment,
height,
}) => {
// 计算每个分组的高度
const getItemHeight = useCallback((index: number) => {
const group = groups[index];
if (!group) return 120; // 默认折叠高度
const groupId = viewMode === MaterialSegmentViewMode.ByClassification
? (group as ClassificationGroup).category
: (group as ModelGroup).model_id;
const isExpanded = expandedGroups.has(groupId);
if (!isExpanded) {
return 120; // 折叠状态的高度
}
// 展开状态的高度计算
const baseHeight = 120; // 头部高度
const segmentCount = group.segments.length;
const segmentsPerRow = 4; // 每行显示的片段数
const rowCount = Math.ceil(segmentCount / segmentsPerRow);
const segmentRowHeight = 280; // 每行片段的高度
const footerHeight = 60; // 底部统计信息高度
return baseHeight + (rowCount * segmentRowHeight) + footerHeight;
}, [groups, viewMode, expandedGroups]);
// 计算总高度
const totalHeight = useMemo(() => {
return groups.reduce((total, _, index) => total + getItemHeight(index), 0);
}, [groups, getItemHeight]);
// 渲染单个分组项
const renderItem = useCallback(({ index, style }: { index: number; style: React.CSSProperties }) => {
const group = groups[index];
if (!group) return null;
return (
<div style={style}>
<div className="px-6 pb-4">
<MaterialSegmentGroup
key={viewMode === MaterialSegmentViewMode.ByClassification
? (group as ClassificationGroup).category
: (group as ModelGroup).model_id}
group={group}
viewMode={viewMode}
isExpanded={expandedGroups.has(
viewMode === MaterialSegmentViewMode.ByClassification
? (group as ClassificationGroup).category
: (group as ModelGroup).model_id
)}
selectedSegmentIds={selectedSegmentIds}
onToggleExpand={onToggleExpand}
onSelectSegment={onSelectSegment}
/>
</div>
</div>
);
}, [groups, viewMode, expandedGroups, selectedSegmentIds, onToggleExpand, onSelectSegment]);
// 如果分组数量较少,不使用虚拟化
if (groups.length <= 10) {
return (
<div className="space-y-4 px-6">
{groups.map((group, _index) => (
<MaterialSegmentGroup
key={viewMode === MaterialSegmentViewMode.ByClassification
? (group as ClassificationGroup).category
: (group as ModelGroup).model_id}
group={group}
viewMode={viewMode}
isExpanded={expandedGroups.has(
viewMode === MaterialSegmentViewMode.ByClassification
? (group as ClassificationGroup).category
: (group as ModelGroup).model_id
)}
selectedSegmentIds={selectedSegmentIds}
onToggleExpand={onToggleExpand}
onSelectSegment={onSelectSegment}
/>
))}
</div>
);
}
return (
<List
height={Math.min(height, totalHeight)}
itemCount={groups.length}
itemSize={(index) => getItemHeight(index)}
width="100%"
className="custom-scrollbar"
>
{renderItem}
</List>
);
};