forked from 77media/video-flow
233 lines
7.4 KiB
TypeScript
233 lines
7.4 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';
|
|
import { useWorkofficeData } from '@/components/pages/work-flow/use-workoffice-data';
|
|
import { useAppSelector } from '@/lib/store/hooks';
|
|
import { storyboardData, productionData, editorData } from '@/components/workflow/work-office/mock-data';
|
|
|
|
interface ScriptModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
currentStage?: number;
|
|
roles: any[];
|
|
currentLoadingText: string;
|
|
}
|
|
|
|
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, currentLoadingText }: ScriptModalProps) {
|
|
const [isPlaying, setIsPlaying] = useState(true);
|
|
const [progress, setProgress] = useState(0);
|
|
const [startTime, setStartTime] = useState<number | null>(null);
|
|
const prevStageRef = useRef(currentStage);
|
|
const [currentContent, setCurrentContent] = useState<Record<string, any>>({});
|
|
|
|
// 将 hook 调用移到组件顶层
|
|
const { scriptwriterData, thinkingText, thinkingColor, sketchType } = useWorkofficeData(currentStage, isOpen, currentLoadingText);
|
|
|
|
|
|
// 根据当前阶段加载对应数据
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
let data: Record<string, any> = {};
|
|
switch (currentStage) {
|
|
case 0:
|
|
data = scriptwriterData;
|
|
break;
|
|
case 1:
|
|
data = storyboardData;
|
|
break;
|
|
case 2:
|
|
data = productionData;
|
|
break;
|
|
case 3:
|
|
data = editorData;
|
|
break;
|
|
}
|
|
console.log('data', data);
|
|
setCurrentContent(data);
|
|
}, [currentStage, isOpen, scriptwriterData]);
|
|
|
|
// 每次打开都重置进度条和时间
|
|
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}
|
|
currentContent={currentContent}
|
|
thinkingText={thinkingText}
|
|
thinkingColor={thinkingColor}
|
|
sketchType={sketchType}
|
|
/>
|
|
</motion.div>
|
|
</motion.div>
|
|
</motion.div>
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|