From 07d5463836c3d8495331b0872a39b48ef2e760d5 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 10 Jul 2025 23:53:26 +0800 Subject: [PATCH] fix: --- python_core/services/template_manager.py | 2 +- .../timeline/SegmentContextMenu.tsx | 105 ++++++++++++++ src/components/timeline/SegmentTooltip.tsx | 135 ++++++++++++++++++ src/components/timeline/TrackTimeline.tsx | 76 +++++++++- src/components/timeline/index.ts | 2 + 5 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 src/components/timeline/SegmentContextMenu.tsx create mode 100644 src/components/timeline/SegmentTooltip.tsx diff --git a/python_core/services/template_manager.py b/python_core/services/template_manager.py index 4e4ce88..5c40311 100644 --- a/python_core/services/template_manager.py +++ b/python_core/services/template_manager.py @@ -429,7 +429,7 @@ class TemplateManager: segment = { 'id': segment_data.get('id', ''), 'type': segment_data.get('type', 'video'), - 'name': segment_data.get('name', f'Segment {len(track["segments"]) + 1}'), + 'name': segment_data.get('name', f'随机'), 'start_time': start_time, 'end_time': end_time, 'duration': duration, diff --git a/src/components/timeline/SegmentContextMenu.tsx b/src/components/timeline/SegmentContextMenu.tsx new file mode 100644 index 0000000..10e4b6f --- /dev/null +++ b/src/components/timeline/SegmentContextMenu.tsx @@ -0,0 +1,105 @@ +import React from 'react' +import { Edit, Copy, Trash2, Info } from 'lucide-react' + +interface SegmentContextMenuProps { + isOpen: boolean + position: { x: number; y: number } + onClose: () => void + onEdit: () => void + onCopy?: () => void + onDelete?: () => void + onInfo?: () => void +} + +export const SegmentContextMenu: React.FC = ({ + isOpen, + position, + onClose, + onEdit, + onCopy, + onDelete, + onInfo +}) => { + React.useEffect(() => { + if (isOpen) { + const handleClickOutside = () => onClose() + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + + document.addEventListener('click', handleClickOutside) + document.addEventListener('keydown', handleEscape) + + return () => { + document.removeEventListener('click', handleClickOutside) + document.removeEventListener('keydown', handleEscape) + } + } + }, [isOpen, onClose]) + + if (!isOpen) return null + + return ( +
e.stopPropagation()} + > + + + {onCopy && ( + + )} + + {onInfo && ( + + )} + + {onDelete && ( + <> +
+ + + )} +
+ ) +} diff --git a/src/components/timeline/SegmentTooltip.tsx b/src/components/timeline/SegmentTooltip.tsx new file mode 100644 index 0000000..f3572c2 --- /dev/null +++ b/src/components/timeline/SegmentTooltip.tsx @@ -0,0 +1,135 @@ +import React from 'react' + +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[] +} + +interface SegmentTooltipProps { + segment: TrackSegment | null + position: { x: number; y: number } + isVisible: boolean +} + +export const SegmentTooltip: React.FC = ({ + segment, + position, + isVisible +}) => { + const formatTime = (seconds: number): string => { + const minutes = Math.floor(seconds / 60) + const secs = (seconds % 60).toFixed(2) + return `${minutes}:${secs.padStart(5, '0')}` + } + + const getTypeLabel = (type: string) => { + switch (type) { + case 'video': return '视频' + case 'audio': return '音频' + case 'image': return '图片' + case 'text': return '文本' + case 'effect': return '特效' + default: return '未知' + } + } + + const getTypeColor = (type: string) => { + switch (type) { + case 'video': return 'bg-blue-100 text-blue-800' + case 'audio': return 'bg-green-100 text-green-800' + case 'image': return 'bg-yellow-100 text-yellow-800' + case 'text': return 'bg-purple-100 text-purple-800' + case 'effect': return 'bg-gray-100 text-gray-800' + default: return 'bg-gray-100 text-gray-800' + } + } + + if (!isVisible || !segment) return null + + return ( +
+ {/* Segment Name */} +
+ + {getTypeLabel(segment.type)} + +

{segment.name}

+
+ + {/* Time Information */} +
+
+
+ 开始时间: +
{formatTime(segment.start_time)}
+
+
+ 结束时间: +
{formatTime(segment.end_time)}
+
+
+ +
+ 持续时长: +
{formatTime(segment.duration)}
+
+ + {/* Resource Path */} + {segment.resource_path && ( +
+ 资源文件: +
+ {segment.resource_path.split('/').pop() || segment.resource_path} +
+
+ )} + + {/* Effects */} + {segment.effects && segment.effects.length > 0 && ( +
+ 特效: +
+ {segment.effects.length} 个特效 +
+
+ )} + + {/* Properties */} + {segment.properties && Object.keys(segment.properties).length > 0 && ( +
+ 属性: +
+ {Object.keys(segment.properties).length} 个属性 +
+
+ )} +
+ + {/* Tooltip Arrow */} +
+
+ ) +} diff --git a/src/components/timeline/TrackTimeline.tsx b/src/components/timeline/TrackTimeline.tsx index 0dc227a..4a1937b 100644 --- a/src/components/timeline/TrackTimeline.tsx +++ b/src/components/timeline/TrackTimeline.tsx @@ -1,4 +1,6 @@ import React from 'react' +import { SegmentContextMenu } from './SegmentContextMenu' +import { SegmentTooltip } from './SegmentTooltip' interface TrackSegment { id: string @@ -40,6 +42,13 @@ export const TrackTimeline: React.FC = ({ }) => { const [editingSegmentId, setEditingSegmentId] = React.useState(null) const [editingName, setEditingName] = React.useState('') + 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 getSegmentColor = (type: string) => { switch (type) { case 'video': return 'bg-blue-500 hover:bg-blue-600' @@ -84,8 +93,13 @@ export const TrackTimeline: React.FC = ({ const handleSegmentRightClick = (e: React.MouseEvent, segment: TrackSegment) => { e.preventDefault() - setEditingSegmentId(segment.id) - setEditingName(segment.name) + e.stopPropagation() + + setContextMenu({ + isOpen: true, + position: { x: e.clientX, y: e.clientY }, + segment + }) } const handleNameSubmit = (segmentId: string) => { @@ -109,6 +123,32 @@ export const TrackTimeline: React.FC = ({ } } + 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 = () => { + if (contextMenu.segment) { + setEditingSegmentId(contextMenu.segment.id) + setEditingName(contextMenu.segment.name) + } + } + return (
{/* Track Header */} @@ -160,8 +200,9 @@ export const TrackTimeline: React.FC = ({ onClick={() => onSegmentClick?.(segment)} onDoubleClick={() => handleSegmentDoubleClick(segment)} onContextMenu={(e) => handleSegmentRightClick(e, segment)} - onMouseEnter={() => onSegmentHover?.(segment)} - onMouseLeave={() => onSegmentHover?.(null)} + 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 ? ( @@ -197,6 +238,33 @@ export const TrackTimeline: React.FC = ({
)}
+ + {/* 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 */} + ) } diff --git a/src/components/timeline/index.ts b/src/components/timeline/index.ts index 119b755..6b49612 100644 --- a/src/components/timeline/index.ts +++ b/src/components/timeline/index.ts @@ -1,2 +1,4 @@ export { TimelineRuler } from './TimelineRuler' export { TrackTimeline } from './TrackTimeline' +export { SegmentContextMenu } from './SegmentContextMenu' +export { SegmentTooltip } from './SegmentTooltip'