video-flow-b/components/ui/script-modal.tsx
2025-07-18 07:05:47 +08:00

196 lines
6.2 KiB
TypeScript

'use client';
import { motion, AnimatePresence } from 'framer-motion';
import { X } from 'lucide-react';
import WorkOffice from '@/components/workflow/work-office/work-office';
import { useState, useEffect, useRef } from 'react';
interface ScriptModalProps {
isOpen: boolean;
onClose: () => void;
currentStage?: number;
roles: any[];
}
const stages = [
{
id: 'script',
title: 'Scriptwriter',
color: '#8b5cf6',
duration: 3 * 60 * 1000 // 3分钟
},
{
id: 'storyboard',
title: 'Storyboard artist',
color: '#06b6d4',
duration: 4 * 60 * 1000 // 4分钟
},
{
id: 'production',
title: 'Visual director',
color: '#10b981',
duration: 5 * 60 * 1000 // 5分钟
},
{
id: 'editing',
title: 'Editor',
color: '#f59e0b',
duration: 15 * 60 * 1000 // 15分钟
}
];
export function ScriptModal({ isOpen, onClose, currentStage = 0, roles }: ScriptModalProps) {
const [isPlaying, setIsPlaying] = useState(true);
const [progress, setProgress] = useState(0);
const [startTime, setStartTime] = useState<number | null>(null);
const prevStageRef = useRef(currentStage);
// 每次打开都重置进度条和时间
useEffect(() => {
setProgress(0);
setIsPlaying(true);
setStartTime(Date.now());
}, [isOpen, currentStage]);
// 处理进度条和时间更新
useEffect(() => {
if (!isPlaying || !isOpen) return;
if (startTime === null) {
setStartTime(Date.now());
}
const currentDuration = stages[currentStage].duration;
const updateInterval = 50; // 更新间隔(毫秒)
const interval = setInterval(() => {
const now = Date.now();
const elapsed = now - (startTime || now);
const newProgress = Math.min((elapsed / currentDuration) * 100, 100);
setProgress(newProgress);
if (newProgress >= 100) {
setIsPlaying(false);
setStartTime(null);
}
}, updateInterval);
return () => clearInterval(interval);
}, [isPlaying, currentStage, startTime, isOpen]);
// 计算剩余时间
const getRemainingTime = () => {
if (!isPlaying || startTime === null) return '0:00';
const currentDuration = stages[currentStage].duration;
const elapsed = Date.now() - startTime;
const remaining = Math.max(0, currentDuration - elapsed);
const minutes = Math.floor(remaining / 60000);
const seconds = Math.floor((remaining % 60000) / 1000);
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
};
return (
<AnimatePresence mode="wait">
{isOpen && (
<>
{/* 背景遮罩 */}
<motion.div
className="fixed inset-0 bg-black/20 backdrop-blur-sm z-50"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
onClick={onClose}
/>
{/* 弹窗内容 */}
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<motion.div
className="relative w-11/12 h-[90vh] bg-white/80 dark:bg-gray-900/80 backdrop-blur-xl rounded-2xl shadow-2xl overflow-hidden"
initial={{ scale: 0.95, y: 10, opacity: 0 }}
animate={{
scale: 1,
y: 0,
opacity: 1,
transition: {
type: "spring",
duration: 0.3,
bounce: 0.15,
stiffness: 300,
damping: 25
}
}}
exit={{
scale: 0.95,
y: 10,
opacity: 0,
transition: {
type: "tween",
duration: 0.1,
ease: "easeOut"
}
}}
>
{/* 关闭按钮 */}
<motion.button
className="absolute z-50 top-4 right-4 p-2 rounded-full bg-gray-100/80 dark:bg-gray-800/80 hover:bg-gray-200/80 dark:hover:bg-gray-700/80 transition-colors"
onClick={onClose}
whileHover={{ rotate: 90 }}
whileTap={{ scale: 0.9 }}
transition={{ duration: 0.1 }}
>
<X className="w-5 h-5 text-gray-600 dark:text-gray-300" />
</motion.button>
{/* 进度条和时间显示 */}
<div className="absolute left-[2rem] right-[2rem] bottom-[3rem] z-50">
<div className="flex items-center justify-between mb-2">
<span className="text-gray-700 dark:text-gray-300 font-medium">
{stages[currentStage].title}
</span>
<div className="flex items-center space-x-4">
<span className="text-gray-600 dark:text-gray-400 text-sm">
Remaining time: {getRemainingTime()}
</span>
<span className="text-gray-600 dark:text-gray-400 text-sm">
{Math.round(progress)}% completed
</span>
</div>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<motion.div
className="h-2 rounded-full transition-all duration-300"
style={{
width: `${progress}%`,
backgroundColor: stages[currentStage].color
}}
/>
</div>
</div>
{/* 内容区域 */}
<motion.div
className="h-full overflow-auto p-6 relative"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.1, duration: 0.2 }}
>
<WorkOffice initialStage={currentStage} roles={roles} />
</motion.div>
</motion.div>
</motion.div>
</>
)}
</AnimatePresence>
);
}