import { useState, useRef, useEffect } from 'react'; import { motion } from 'framer-motion'; import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { Trash2, Copy, RefreshCw, GripVertical } from 'lucide-react'; import { StoryboardScene } from '../pages/storyboard-view'; import Image from 'next/image'; interface StoryboardCardProps { scene: StoryboardScene; index: number; isSelected?: boolean; onUpdate: (updates: Partial) => void; onDelete: () => void; onDuplicate: () => void; } export function StoryboardCard({ scene, index, isSelected = false, onUpdate, onDelete, onDuplicate }: StoryboardCardProps) { const [isEditing, setIsEditing] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showSaveIndicator, setShowSaveIndicator] = useState(false); const [isHovered, setIsHovered] = useState(false); const cardRef = useRef(null); const timeoutRef = useRef(); const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: scene.id }); const style = { transform: CSS.Transform.toString(transform), transition, }; // 处理自动保存 const handleContentChange = ( field: keyof StoryboardScene, value: string, element: HTMLElement ) => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } timeoutRef.current = setTimeout(() => { onUpdate({ [field]: value }); setShowSaveIndicator(true); setTimeout(() => setShowSaveIndicator(false), 2000); }, 500); }; // 组件卸载时清理定时器 useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); // 选中效果 useEffect(() => { if (isSelected && cardRef.current) { cardRef.current.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }); } }, [isSelected]); return (
setIsHovered(true)} onMouseLeave={() => { setIsHovered(false); setShowDeleteConfirm(false); }} > setIsHovered(true)} onMouseLeave={() => { setIsHovered(false); setShowDeleteConfirm(false); }} > {/* Drag Handle */}
{/* Scene Image */}
{scene.name}
{/* Content Area */}
{/* Scrollable Content */}
{/* Scene Name */}
setIsEditing(true)} onBlur={(e) => { setIsEditing(false); handleContentChange('name', e.currentTarget.textContent || '', e.currentTarget); }} className="outline-none focus:bg-white/5 rounded px-2 py-1 transition-colors" dangerouslySetInnerHTML={{ __html: scene.name }} /> {/* Scene Description */}
setIsEditing(true)} onBlur={(e) => { setIsEditing(false); handleContentChange('description', e.currentTarget.textContent || '', e.currentTarget); }} className="outline-none focus:bg-white/5 rounded px-2 py-1 transition-colors" dangerouslySetInnerHTML={{ __html: scene.description }} /> {/* Shot Description */}
setIsEditing(true)} onBlur={(e) => { setIsEditing(false); handleContentChange('shot', e.currentTarget.textContent || '', e.currentTarget); }} className="outline-none focus:bg-white/5 rounded px-2 py-1 transition-colors text-white/80" dangerouslySetInnerHTML={{ __html: scene.shot }} />
{/* Frame Description */}
setIsEditing(true)} onBlur={(e) => { setIsEditing(false); handleContentChange('frame', e.currentTarget.textContent || '', e.currentTarget); }} className="outline-none focus:bg-white/5 rounded px-2 py-1 transition-colors text-white/80" dangerouslySetInnerHTML={{ __html: scene.frame }} />
{/* Atmosphere */}
setIsEditing(true)} onBlur={(e) => { setIsEditing(false); handleContentChange('atmosphere', e.currentTarget.textContent || '', e.currentTarget); }} className="outline-none focus:bg-white/5 rounded px-2 py-1 transition-colors text-white/80" dangerouslySetInnerHTML={{ __html: scene.atmosphere }} />
{/* Floating Action Bar */} {/* Delete Button */} { e.stopPropagation(); setShowDeleteConfirm(true); }} > {/* Delete Confirmation */} {showDeleteConfirm && (
e.stopPropagation()} > Confirm delete?
)}
{/* Duplicate Button */} { e.stopPropagation(); onDuplicate(); }} className="p-2 rounded-lg hover:bg-white/10 transition-colors" > {/* Regenerate Button */} e.stopPropagation()} className="p-2 rounded-lg hover:bg-white/10 transition-colors" >
{/* Save Indicator */} Saved
); }