'use client'; import React, { useRef, useEffect, useState, useCallback } from 'react'; import { motion } from 'framer-motion'; import { Skeleton } from '@/components/ui/skeleton'; import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal'; import { Loader2, X } from 'lucide-react'; import { TaskObject } from '@/api/DTO/movieEdit'; interface ThumbnailGridProps { isEditModalOpen: boolean; taskObject: TaskObject; isLoading: boolean; currentSketchIndex: number; taskSketch: any[]; taskVideos: any[]; isGeneratingSketch: boolean; isGeneratingVideo: boolean; sketchCount: number; totalSketchCount: number; onSketchSelect: (index: number) => void; } export function ThumbnailGrid({ isEditModalOpen, taskObject, isLoading, currentSketchIndex, taskSketch, taskVideos, isGeneratingSketch, isGeneratingVideo, sketchCount, totalSketchCount, onSketchSelect }: ThumbnailGridProps) { 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 thumbnailWidth = container.offsetWidth / 4; // 每个缩略图宽度(包含间距) const scrollPosition = currentSketchIndex * thumbnailWidth; 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') { return taskObject.scenes.data; } else if (taskObject.currentStage === 'shot_sketch') { return taskObject.shot_sketch.data; } return []; }, [taskObject.currentStage, taskObject.videos.data, taskObject.scenes.data, taskObject.shot_sketch.data]); // 使用 useRef 存储前一次的数据,避免触发重渲染 const prevDataRef = useRef([]); useEffect(() => { const currentData = getCurrentData(); if (currentData && currentData.length > 0) { const currentDataStr = JSON.stringify(currentData); const prevDataStr = JSON.stringify(prevDataRef.current); // 只有当数据真正发生变化时才进行处理 if (currentDataStr !== prevDataStr) { // 找到最新更新的数据项的索引 const changedIndex = currentData.findIndex((item, index) => { // 检查是否是新增的数据 if (index >= prevDataRef.current.length) return true; // 检查数据是否发生变化(包括状态变化) return JSON.stringify(item) !== JSON.stringify(prevDataRef.current[index]); }); console.log('changedIndex_thumbnail-grid', changedIndex, 'currentData:', currentData, 'prevData:', prevDataRef.current); // 如果找到变化的项,自动选择该项 if (changedIndex !== -1) { onSketchSelect(changedIndex); } // 更新前一次的数据快照 prevDataRef.current = JSON.parse(JSON.stringify(currentData)); } } }, [taskObject, getCurrentData, onSketchSelect]); // 处理键盘左右键事件 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 && !isEditModalOpen) { thumbnailsRef.current.focus(); } window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [handleKeyDown]); // 确保在数据变化时保持焦点 useEffect(() => { if (thumbnailsRef.current && !isFocused && !isEditModalOpen) { thumbnailsRef.current.focus(); } }, [taskObject.currentStage, isFocused]); // 处理鼠标/触摸拖动事件 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]); // 渲染加载状态 if (isLoading) { return ( <> ); } // 粗剪/精剪最终成片阶段不显示缩略图 if (taskObject.currentStage === 'final_video') { return null; } // 渲染生成中的缩略图 const renderGeneratingThumbnail = () => { const currentSketch = taskSketch[currentSketchIndex]; const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)']; const bgColors = currentSketch?.bg_rgb || defaultBgColors; return ( {/* 动态渐变背景 */} {/* 动态光效 */}
Scene {sketchCount + 1}
); }; // 渲染视频阶段的缩略图 const renderVideoThumbnails = () => ( taskObject.videos.data.map((video, index) => { return (
!isDragging && onSketchSelect(index)} > {/* 视频层 */}
{taskObject.videos.data[index].video_status === 0 && (
Generating...
)} {taskObject.videos.data[index].video_status === 2 && (
Failed
)} {taskObject.videos.data[index].urls ? (
Scene {index + 1}
); }) ); // 渲染分镜草图阶段的缩略图 const renderSketchThumbnails = (sketchData: any[]) => ( <> {sketchData.map((sketch, index) => { return (
!isDragging && onSketchSelect(index)} > {/* 状态 */} {sketch.status === 0 && (
Generating...
)} {sketch.status === 2 && (
Failed
)} {/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */} {(sketch.status === 1) && (
{`NG
)}
Scene {index + 1}
); })} {isGeneratingSketch && sketchCount < totalSketchCount && renderGeneratingThumbnail()} ); return (
setIsDragging(false)} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} > {taskObject.currentStage === 'video' && renderVideoThumbnails()} {taskObject.currentStage === 'scene' && renderSketchThumbnails(taskObject.scenes.data)} {taskObject.currentStage === 'shot_sketch' && renderSketchThumbnails(taskObject.shot_sketch.data)} {taskObject.currentStage === 'character' && renderSketchThumbnails(taskObject.scenes.data)}
); }