import React from 'react' import { SegmentContextMenu } from './SegmentContextMenu' import { SegmentTooltip } from './SegmentTooltip' import { ResourceCategoryServiceV2, ResourceCategoryV2 } from '../../services/resourceCategoryServiceV2' export interface TrackSegment { id: string type: 'video' | 'audio' | 'image' | 'text' | 'effect' name: string start_time: number end_time: number duration: number resource_path?: string properties?: any effects?: any[] } export interface Track { id: string name: string type: 'video' | 'audio' | 'effect' | 'text' | 'sticker' | 'image' index: number segments: TrackSegment[] properties?: any } interface TrackTimelineProps { track: Track totalDuration: number currentTime: number onSegmentClick?: (segment: TrackSegment) => void onSegmentHover?: (segment: TrackSegment | null) => void onSegmentNameChange?: (segmentId: string, newName: string) => void } export const TrackTimeline: React.FC = ({ track, totalDuration, currentTime, onSegmentClick, onSegmentHover, onSegmentNameChange }) => { const [editingSegmentId, setEditingSegmentId] = React.useState(null) const [contextMenu, setContextMenu] = React.useState<{ isOpen: boolean position: { x: number; y: number } segment: TrackSegment | null }>({ isOpen: false, position: { x: 0, y: 0 }, segment: null }) const [hoveredSegment, setHoveredSegment] = React.useState(null) const [mousePosition, setMousePosition] = React.useState({ x: 0, y: 0 }) const [categories, setCategories] = React.useState([]) const [loadingCategories, setLoadingCategories] = React.useState(false) // 加载分类数据 const loadCategories = React.useCallback(async () => { try { setLoadingCategories(true) const result = await ResourceCategoryServiceV2.getAllCategories({ include_cloud: true }) setCategories(result) } catch (error) { console.error('Failed to load categories:', error) } finally { setLoadingCategories(false) } }, []) // 组件挂载时加载分类 React.useEffect(() => { loadCategories() }, []) const getSegmentColor = (type: string) => { switch (type) { case 'video': return 'bg-blue-500 hover:bg-blue-600' case 'audio': return 'bg-green-500 hover:bg-green-600' case 'image': return 'bg-yellow-500 hover:bg-yellow-600' case 'text': return 'bg-purple-500 hover:bg-purple-600' case 'effect': return 'bg-gray-500 hover:bg-gray-600' default: return 'bg-gray-400 hover:bg-gray-500' } } const getSegmentTextColor = (type: string) => { switch (type) { case 'video': return 'text-blue-100' case 'audio': return 'text-green-100' case 'image': return 'text-yellow-100' case 'text': return 'text-purple-100' case 'effect': return 'text-gray-100' default: return 'text-gray-100' } } const getTrackTypeColor = (type: string) => { switch (type) { case 'video': return 'bg-blue-100 text-blue-800 border-blue-200' case 'audio': return 'bg-green-100 text-green-800 border-green-200' case 'subtitle': return 'bg-purple-100 text-purple-800 border-purple-200' default: return 'bg-gray-100 text-gray-800 border-gray-200' } } const formatTime = (seconds: number): string => { const minutes = Math.floor(seconds / 60) const secs = (seconds % 60).toFixed(2) return `${minutes}:${secs.padStart(5, '0')}` } const handleSegmentDoubleClick = async (segment: TrackSegment) => { setEditingSegmentId(segment.id) // 确保分类数据已加载 if (categories.length === 0 && !loadingCategories) { await loadCategories() } } const handleSegmentRightClick = (e: React.MouseEvent, segment: TrackSegment) => { e.preventDefault() e.stopPropagation() setContextMenu({ isOpen: true, position: { x: e.clientX, y: e.clientY }, segment }) } const handleCategorySelect = (segmentId: string, categoryTitle: string) => { if (onSegmentNameChange && categoryTitle) { onSegmentNameChange(segmentId, categoryTitle) } setEditingSegmentId(null) } const handleSegmentMouseEnter = (e: React.MouseEvent, segment: TrackSegment) => { setHoveredSegment(segment) setMousePosition({ x: e.clientX, y: e.clientY }) onSegmentHover?.(segment) } const handleSegmentMouseLeave = () => { setHoveredSegment(null) onSegmentHover?.(null) } const handleSegmentMouseMove = (e: React.MouseEvent) => { setMousePosition({ x: e.clientX, y: e.clientY }) } const handleContextMenuClose = () => { setContextMenu({ isOpen: false, position: { x: 0, y: 0 }, segment: null }) } const handleContextMenuEdit = async () => { if (contextMenu.segment) { setEditingSegmentId(contextMenu.segment.id) // 确保分类数据已加载 if (categories.length === 0 && !loadingCategories) { await loadCategories() } } } return (
{/* Track Header */}
{track.type === 'video' ? '视频' : track.type === 'audio' ? '音频' : '字幕'}

{track.type}轨道 {track.index}: {track.name}

{track.segments.length} 个片段
{/* Timeline */}
{/* Background grid */}
{Array.from({ length: Math.floor(totalDuration) + 1 }, (_, i) => (
))}
{/* Segments */} {track.segments.map((segment) => { const startPercent = (segment.start_time / totalDuration) * 100 const widthPercent = (segment.duration / totalDuration) * 100 return (
onSegmentClick?.(segment)} onDoubleClick={() => handleSegmentDoubleClick(segment)} onContextMenu={(e) => handleSegmentRightClick(e, segment)} onMouseEnter={(e) => handleSegmentMouseEnter(e, segment)} onMouseLeave={handleSegmentMouseLeave} onMouseMove={handleSegmentMouseMove} title={`${segment.name}\n类型: ${segment.type}\n开始: ${formatTime(segment.start_time)}\n结束: ${formatTime(segment.end_time)}\n时长: ${formatTime(segment.duration)}${segment.resource_path ? `\n资源: ${segment.resource_path}` : ''}`} > {editingSegmentId === segment.id ? (
e.stopPropagation()}> {loadingCategories ? (
加载分类中...
) : ( )}
) : (
{segment.name}
)}
) })} {/* Current time indicator */}
{/* Empty track indicator */} {track.segments.length === 0 && (
该轨道暂无片段
)}
{/* Context Menu */} { // TODO: Implement copy functionality console.log('Copy segment:', contextMenu.segment?.name) }} onDelete={() => { // TODO: Implement delete functionality console.log('Delete segment:', contextMenu.segment?.name) }} onInfo={() => { // TODO: Implement info functionality console.log('Show info for segment:', contextMenu.segment?.name) }} /> {/* Segment Tooltip */}
) }