/** * 视频编辑覆盖层组件 * 处理视频点击事件,管理编辑点的显示和交互 */ import React, { useCallback, useRef, useEffect, useState, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { EditPoint } from './EditPoint'; import { EditConnection, calculateInputPosition } from './EditConnection'; import { EditInput } from './EditInput'; import { EditDescription } from './EditDescription'; import { useVideoEdit } from './useVideoEdit'; import { EditPoint as EditPointType, EditPointPosition, EditPointStatus } from './types'; interface VideoEditOverlayProps { /** 项目ID */ projectId: string; /** 用户ID */ userId: number; /** 当前视频信息 */ currentVideo?: { id: string; url: string; duration: number; } | null; /** 视频元素引用 */ videoRef: React.RefObject; /** 是否启用编辑模式 */ enabled?: boolean; /** 编辑描述提交回调 */ onDescriptionSubmit?: (editPoint: EditPointType, description: string) => void; /** 编辑点变化回调 */ onEditPointsChange?: (editPoints: EditPointType[]) => void; /** 样式类名 */ className?: string; } /** * 视频编辑覆盖层组件 */ export const VideoEditOverlay: React.FC = ({ projectId, userId, currentVideo, videoRef, enabled = true, onDescriptionSubmit, onEditPointsChange, className = '' }) => { const containerRef = useRef(null); const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); const [activeInputId, setActiveInputId] = useState(null); // 使用视频编辑Hook const { context, createEditPoint, updateEditPoint, deleteEditPoint, selectEditPoint, toggleEditMode, toggleInput, submitDescription } = useVideoEdit({ projectId, userId, currentVideo, onEditPointsChange, onDescriptionSubmit }); const { editPoints, isEditMode, selectedEditPointId } = context; // 更新容器尺寸 const updateContainerSize = useCallback(() => { if (containerRef.current) { const rect = containerRef.current.getBoundingClientRect(); setContainerSize({ width: rect.width, height: rect.height }); } }, []); // 监听容器尺寸变化 useEffect(() => { updateContainerSize(); const resizeObserver = new ResizeObserver(updateContainerSize); if (containerRef.current) { resizeObserver.observe(containerRef.current); } return () => resizeObserver.disconnect(); }, [updateContainerSize]); // 处理视频点击事件 const handleVideoClick = useCallback((e: React.MouseEvent) => { if (!enabled || !isEditMode || !currentVideo || !containerRef.current || !videoRef.current) { return; } // 检查点击目标,避免在编辑点或其子元素上创建新编辑点 const target = e.target as HTMLElement; const isEditPointClick = target.closest('[data-edit-point]') !== null; const isDescriptionClick = target.closest('[data-edit-description]') !== null; const isInputClick = target.closest('[data-edit-input]') !== null; if (isEditPointClick || isDescriptionClick || isInputClick) { return; // 不处理编辑点相关元素的点击 } // 阻止事件冒泡,避免触发视频播放控制 e.stopPropagation(); e.preventDefault(); const rect = containerRef.current.getBoundingClientRect(); const x = ((e.clientX - rect.left) / rect.width) * 100; const y = ((e.clientY - rect.top) / rect.height) * 100; // 获取当前视频时间 const currentTime = videoRef.current.currentTime || 0; // 创建编辑点 createEditPoint({ x, y }, currentTime); }, [enabled, isEditMode, currentVideo, createEditPoint, videoRef]); // 处理编辑点点击 const handleEditPointClick = useCallback((editPoint: EditPointType) => { selectEditPoint(editPoint.id); setActiveInputId(editPoint.id); toggleInput(editPoint.id, true); }, [selectEditPoint, toggleInput]); // 处理编辑点删除 const handleEditPointDelete = useCallback((id: string) => { deleteEditPoint(id); if (activeInputId === id) { setActiveInputId(null); } }, [deleteEditPoint, activeInputId]); // 处理编辑点编辑 const handleEditPointEdit = useCallback((id: string) => { setActiveInputId(id); toggleInput(id, true); }, [toggleInput]); // 处理描述提交 const handleDescriptionSubmit = useCallback(async (id: string, description: string) => { const success = await submitDescription(id, description); if (success) { // 提交成功后隐藏输入框并清除活动状态 toggleInput(id, false); setActiveInputId(null); } }, [submitDescription, toggleInput]); // 处理输入取消 const handleInputCancel = useCallback((id: string) => { toggleInput(id, false); setActiveInputId(null); // 如果是新创建的编辑点且没有描述,删除它 const editPoint = editPoints.find(point => point.id === id); if (editPoint && !editPoint.description.trim()) { deleteEditPoint(id); } }, [toggleInput, editPoints, deleteEditPoint]); // 计算输入框和描述框位置 const elementPositions = useMemo(() => { const positions: Record = {}; editPoints.forEach(editPoint => { if (containerSize.width > 0 && containerSize.height > 0) { const absolutePosition = { x: (editPoint.position.x / 100) * containerSize.width, y: (editPoint.position.y / 100) * containerSize.height }; positions[editPoint.id] = calculateInputPosition( absolutePosition, containerSize ); } }); return positions; }, [editPoints, containerSize]); // 键盘事件处理 useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && activeInputId) { handleInputCancel(activeInputId); } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [activeInputId, handleInputCancel]); // 如果未启用或没有当前视频,不渲染 if (!enabled || !currentVideo) { return null; } return (
{/* 编辑点渲染 */} {editPoints.map(editPoint => ( ))} {/* 连接线渲染 - 仅用于输入框 */} {editPoints.map(editPoint => { const elementPosition = elementPositions[editPoint.id]; if (!editPoint.showInput || !elementPosition) return null; const startPoint = { x: (editPoint.position.x / 100) * containerSize.width, y: (editPoint.position.y / 100) * containerSize.height }; return ( ); })} {/* 输入框渲染 */} {editPoints.map(editPoint => { const elementPosition = elementPositions[editPoint.id]; if (!editPoint.showInput || !elementPosition) return null; return ( handleDescriptionSubmit(editPoint.id, description)} onCancel={() => handleInputCancel(editPoint.id)} /> ); })} {/* 已提交描述显示 */} {editPoints.map(editPoint => { const elementPosition = elementPositions[editPoint.id]; // 只显示已提交且有描述的编辑点 if ( !editPoint.description || editPoint.description.trim() === '' || editPoint.status === EditPointStatus.PENDING || editPoint.showInput || !elementPosition ) return null; return ( { console.log('Description clicked:', editPoint); }} onEdit={(id) => { setActiveInputId(id); toggleInput(id, true); }} onDelete={handleEditPointDelete} /> ); })} {/* 编辑模式提示 */} {isEditMode && editPoints.length === 0 && ( Click anywhere on the video to add an edit point )}
); };