'use client'; import React, { useRef, useEffect, useState, useCallback } from 'react'; import { CircleAlert, Film } from 'lucide-react'; import { TaskObject } from '@/api/DTO/movieEdit'; import { getFirstFrame } from '@/utils/tools'; import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector'; interface ThumbnailGridProps { isDisabledFocus: boolean; taskObject: TaskObject; currentSketchIndex: number; onSketchSelect: (index: number) => void; onRetryVideo: (video_id: string) => void; className: string; selectedView?: 'final' | 'video' | null; aspectRatio: AspectRatioValue; cols: string; isMobile: boolean; } /** * 视频缩略图网格组件,支持hover时播放视频预览 */ export function ThumbnailGrid({ isDisabledFocus, taskObject, currentSketchIndex, onSketchSelect, onRetryVideo, className, selectedView, aspectRatio, cols, isMobile }: ThumbnailGridProps) { const [hoveredIndex, setHoveredIndex] = useState(null); /** 处理鼠标进入缩略图事件 */ const handleMouseEnter = useCallback((index: number) => { setHoveredIndex(index); }, []); /** 处理鼠标离开缩略图事件 */ const handleMouseLeave = useCallback((index: number) => { setHoveredIndex(null); }, []); const thumbnailsRef = useRef(null); const [isDragging, setIsDragging] = useState(false); const [startX, setStartX] = useState(0); const [scrollLeft, setScrollLeft] = useState(0); const [isFocused, setIsFocused] = useState(false); // 监听当前选中索引变化,自动滚动到对应位置(居中显示) useEffect(() => { if (thumbnailsRef.current) { const container = thumbnailsRef.current; const thumbnails = container.children; if (currentSketchIndex >= 0 && currentSketchIndex < thumbnails.length) { const thumbnail = thumbnails[currentSketchIndex] as HTMLElement; const containerRect = container.getBoundingClientRect(); const thumbnailRect = thumbnail.getBoundingClientRect(); // 计算滚动位置:将缩略图居中显示 const containerCenter = containerRect.width / 2; const thumbnailCenter = thumbnailRect.width / 2; const scrollPosition = container.scrollLeft + (thumbnailRect.left - containerRect.left) - containerCenter + thumbnailCenter; container.scrollTo({ left: scrollPosition, behavior: 'smooth' }); } } }, [currentSketchIndex]); // 获取当前阶段的数据数组 const getCurrentData = useCallback(() => { if (taskObject.currentStage === 'video') { return taskObject.videos.data; } else if (taskObject.currentStage === 'scene' || taskObject.currentStage === 'character') { // 为 roles 和 scenes 数据添加唯一标识前缀,避免重复 const rolesWithPrefix = taskObject.roles.data.map((role, index) => ({ ...role, uniqueId: `role_${index}` })); const scenesWithPrefix = taskObject.scenes.data.map((scene, index) => ({ ...scene, uniqueId: `scene_${index}` })); return [...rolesWithPrefix, ...scenesWithPrefix]; } return []; }, [taskObject.currentStage, taskObject.videos.data, taskObject.roles.data, taskObject.scenes.data]); /** Store previous status snapshot for change detection */ const prevStatusRef = useRef>([]); useEffect(() => { const currentData = getCurrentData(); if (!currentData || currentData.length === 0) return; // Extract status fields only to detect meaningful changes const currentStatuses: Array = currentData.map((item: any) => ( taskObject.currentStage === 'video' ? item?.video_status : item?.status )); const prevStatuses = prevStatusRef.current; // Find first changed or newly added index let changedIndex = -1; for (let i = 0; i < currentStatuses.length; i += 1) { if (i >= prevStatuses.length) { changedIndex = i; // new item break; } if (currentStatuses[i] !== prevStatuses[i]) { changedIndex = i; // status changed break; } } if (changedIndex !== -1) { onSketchSelect(changedIndex); } // Update snapshot prevStatusRef.current = currentStatuses.slice(); }, [taskObject, getCurrentData]); // 处理键盘左右键事件 const handleKeyDown = useCallback((e: KeyboardEvent) => { const currentData = getCurrentData(); const maxIndex = currentData.length - 1; console.log('handleKeyDown', maxIndex, 'isFocused:', isFocused); if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && maxIndex >= 0) { e.preventDefault(); let newIndex = currentSketchIndex; if (e.key === 'ArrowLeft') { // 向左循环 newIndex = currentSketchIndex === 0 ? maxIndex : currentSketchIndex - 1; } else { // 向右循环 newIndex = currentSketchIndex === maxIndex ? 0 : currentSketchIndex + 1; } console.log('切换索引:', currentSketchIndex, '->', newIndex, '最大索引:', maxIndex); onSketchSelect(newIndex); } }, [isFocused, currentSketchIndex, onSketchSelect, getCurrentData]); // 监听键盘事件 useEffect(() => { // 组件挂载时自动聚焦 if (thumbnailsRef.current && !isDisabledFocus) { window.addEventListener('keydown', handleKeyDown); } return () => window.removeEventListener('keydown', handleKeyDown); }, [handleKeyDown, isDisabledFocus]); // 处理鼠标/触摸拖动事件 const handleMouseDown = (e: React.MouseEvent) => { // 阻止默认的拖拽行为 e.preventDefault(); setIsDragging(true); setStartX(e.pageX - thumbnailsRef.current!.offsetLeft); setScrollLeft(thumbnailsRef.current!.scrollLeft); }; const handleMouseMove = (e: React.MouseEvent) => { if (!isDragging) return; e.preventDefault(); const x = e.pageX - thumbnailsRef.current!.offsetLeft; const walk = (x - startX) * 2; thumbnailsRef.current!.scrollLeft = scrollLeft - walk; }; const handleMouseUp = (e: React.MouseEvent) => { setIsDragging(false); if (!isDragging) return; }; // 监听阶段变化 useEffect(() => { console.log('taskObject.currentStage_thumbnail-grid', taskObject.currentStage); }, [taskObject.currentStage]); // 渲染视频阶段的缩略图 const renderVideoThumbnails = (disabled: boolean = false) => ( <> {/* 最终视频缩略图(排在第一位) */} {taskObject?.final?.url && (
!isDragging && !disabled && onSketchSelect(-1)} >
{/* 视频层 */}
handleMouseEnter(-1)} onMouseLeave={() => handleMouseLeave(-1)} > final video thumbnail {hoveredIndex === -1 && (
{/* 最终视频徽标 */}
)} {/* 普通视频缩略图 */} {taskObject.videos.data.map((video, index) => { const urls: string = video.urls ? video.urls.join(',') : ''; return (
!isDragging && !disabled && onSketchSelect(index)} > {/* 视频层 */}
{taskObject.videos.data[index].video_status === 0 && (
)} {taskObject.videos.data[index].video_status === 2 && (
)} {taskObject.videos.data[index].urls && taskObject.videos.data[index].urls.length > 0 ? ( //
); })} ); // 渲染分镜草图阶段的缩略图 const renderSketchThumbnails = (sketchData: any[]) => ( <> {sketchData.map((sketch, index) => { return (
!isDragging && onSketchSelect(index)} > {/* 状态 */} {sketch.status === 0 && (
)} {sketch.status === 2 && (
)} {/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */} {(sketch.status === 1) && (
{sketch.type
)} {/* 极简圆形预览,不显示类型徽标 */} {/*
{sketch.type === 'role' ? 'Role' : (sketch.type === 'scene' ? 'Scene' : 'Shot')} {index + 1}
*/}
); })} ); return (
setIsDragging(false)} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} > {(taskObject.currentStage === 'video' || taskObject.currentStage === 'final_video') && renderVideoThumbnails()} {(taskObject.currentStage === 'scene' || taskObject.currentStage === 'character') && renderSketchThumbnails(getCurrentData())}
); }