video-flow-b/components/ui/edit-modal.tsx

406 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import React, { useState, useEffect, SetStateAction, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { X, Image, Users, Video, Music, Settings, FileText, Undo2, TriangleAlert } from 'lucide-react';
import { cn } from '@/public/lib/utils';
import { ScriptTabContent } from './script-tab-content';
import { SceneTabContent } from './scene-tab-content';
import { ShotTabContent } from './shot-tab-content';
import { SettingsTabContent } from './settings-tab-content';
import { CharacterTabContent } from './character-tab-content';
import { MusicTabContent } from './music-tab-content';
import FloatingGlassPanel from './FloatingGlassPanel';
interface EditModalProps {
isOpen: boolean;
onClose: () => void;
activeEditTab: string;
taskStatus: string;
taskSketch: any[];
sketchVideo: any[];
taskScenes: any[];
currentSketchIndex: number;
onSketchSelect: (index: number) => void;
roles?: any[];
music?: any;
setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void;
isPauseWorkFlow: boolean;
fallbackToStep: any;
originalText?: string;
}
const tabs = [
{ id: '0', label: 'Script', icon: FileText },
{ id: '1', label: 'Character', icon: Users },
{ id: '2', label: 'Scene', icon: Image },
{ id: '3', label: 'Video', icon: Video },
{ id: '4', label: 'Music', icon: Music },
// { id: '5', label: '剪辑', icon: Scissors },
{ id: 'settings', label: 'Settings', icon: Settings },
];
export function EditModal({
isOpen,
onClose,
activeEditTab,
taskStatus,
taskSketch,
sketchVideo,
taskScenes,
currentSketchIndex,
onSketchSelect,
roles = [],
music,
setIsPauseWorkFlow,
isPauseWorkFlow,
fallbackToStep,
originalText
}: EditModalProps) {
const [activeTab, setActiveTab] = useState(activeEditTab);
const [currentIndex, setCurrentIndex] = useState(currentSketchIndex);
const [currentRoleIndex, setCurrentRoleIndex] = useState(0);
const [isRemindFallbackOpen, setIsRemindFallbackOpen] = useState(false);
const [isRemindResetOpen, setIsRemindResetOpen] = useState(false);
const [resetKey, setResetKey] = useState(0);
const [remindFallbackText, setRemindFallbackText] = useState('The task will be regenerated and edited. Do you want to continue?');
const scriptTabContentRef = useRef<any>(null);
// 添加一个状态来标记是否是从切换tab触发的提醒
const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null);
useEffect(() => {
setCurrentIndex(currentSketchIndex);
}, [isOpen]);
// 当 activeEditTab 改变时更新 activeTab
useEffect(() => {
setActiveTab(activeEditTab);
}, [activeEditTab]);
const isTabDisabled = (tabId: string) => {
if (tabId === 'settings') return false;
// 换成 如果对应标签下 数据存在 就不禁用
// if (tabId === '1') return roles.length === 0;
if (tabId === '2') return taskScenes.length === 0;
if (tabId === '3') return sketchVideo.length === 0;
if (tabId === '4') return false;
return false;
};
const hanldeChangeSelect = (index: number) => {
if (activeTab === '1') {
setCurrentRoleIndex(index);
} else {
setCurrentIndex(index);
}
}
const checkUpdate = (tabId: string) => {
if (tabId === '0') {
const scriptTabContent = scriptTabContentRef.current;
if (scriptTabContent) {
return scriptTabContent.checkUpdate();
}
}
return false;
}
const handleChangeTab = (tabId: string, disabled: boolean) => {
if (disabled) return;
// 切换前 检查是否更新
const isUpdate = checkUpdate(activeTab);
console.log('contentEditableRef---isUpdate', isUpdate);
if (isUpdate) {
setPendingSwitchTabId(tabId); // 记录要切换到的目标tab
setRemindFallbackText('You must click Apply button to save the current changes.');
setIsRemindFallbackOpen(true);
return;
}
setActiveTab(tabId);
setCurrentIndex(0);
}
const handleSave = () => {
console.log('handleSave');
setIsRemindFallbackOpen(true);
}
const handleConfirmGotoFallback = () => {
console.log('handleConfirmGotoFallback');
if (activeTab === '0') {
fallbackToStep('0');
// 应用剧本
} else {
fallbackToStep('1');
}
}
const handleCloseRemindFallbackPanel = () => {
if (pendingSwitchTabId) {
// 如果是从切换tab触发的提醒关闭后执行tab切换
setActiveTab(pendingSwitchTabId);
setCurrentIndex(0);
setPendingSwitchTabId(null); // 清除记录
}
setIsRemindFallbackOpen(false);
}
const handleReset = () => {
console.log('handleReset');
// 重置当前tab修改的数据
setIsRemindResetOpen(true);
}
const handleConfirmReset = () => {
console.log('handleConfirmReset');
setIsRemindResetOpen(false);
setResetKey(resetKey + 1);
}
const renderTabContent = () => {
switch (activeTab) {
case '0':
return (
<ScriptTabContent
ref={scriptTabContentRef}
setIsPauseWorkFlow={setIsPauseWorkFlow}
isPauseWorkFlow={isPauseWorkFlow}
originalText={originalText}
/>
);
case '1':
return (
<CharacterTabContent
taskSketch={taskSketch}
currentRoleIndex={currentRoleIndex}
onSketchSelect={hanldeChangeSelect}
roles={roles}
/>
);
case '2':
return (
<SceneTabContent
taskSketch={taskScenes}
currentSketchIndex={currentIndex}
onSketchSelect={hanldeChangeSelect}
/>
);
case '3':
return (
<ShotTabContent
currentSketchIndex={currentIndex}
roles={roles}
/>
);
case '4':
return (
<MusicTabContent
taskSketch={taskSketch}
currentSketchIndex={currentIndex}
onSketchSelect={onSketchSelect}
music={music}
/>
);
case 'settings':
return (
<SettingsTabContent
onSettingChange={(key, value) => {
console.log('Setting changed:', key, value);
// TODO: 实现设置更新逻辑
}}
/>
);
default:
return (
<div className="min-h-[400px] flex items-center justify-center text-white/50">
{tabs.find(tab => tab.id === activeTab)?.label} Content area
</div>
);
}
};
return (
<AnimatePresence>
{isOpen && (
<>
{/* 背景遮罩 */}
<motion.div
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
/>
{/* 弹窗内容 */}
<div className="fixed inset-x-0 bottom-0 z-50 flex justify-center">
<motion.div
className={cn(
"w-[88%] min-w-[888px] h-[95vh] bg-[#1a1b1e] rounded-t-2xl overflow-hidden"
)}
initial={{ y: '100%' }}
animate={{ y: 0 }}
exit={{ y: '100%' }}
transition={{
type: 'spring',
damping: 25,
stiffness: 200,
}}
>
{/* 标签栏 */}
<div className="flex items-center gap-1 p-2 overflow-x-auto hide-scrollbar bg-white/5">
<div className="flex-1 flex gap-1">
{tabs.map((tab) => {
const Icon = tab.icon;
const disabled = isTabDisabled(tab.id);
return (
<motion.button
key={tab.id}
className={cn(
'flex items-center gap-2 px-4 py-2 rounded-lg transition-colors relative',
activeTab === tab.id ? 'text-white' : 'text-white/50',
disabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-white/10',
)}
onClick={() => handleChangeTab(tab.id, disabled)}
whileHover={disabled ? undefined : { scale: 1.02 }}
whileTap={disabled ? undefined : { scale: 0.98 }}
>
<Icon className="w-4 h-4" />
<span>{tab.label}</span>
{activeTab === tab.id && (
<motion.div
className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-500"
layoutId="activeTab"
transition={{ type: 'spring', damping: 20 }}
/>
)}
</motion.button>
);
})}
</div>
{/* 弹窗操作按钮 */}
<div className="pl-4 border-l border-white/10">
{/* 关闭按钮 */}
<motion.button
className="p-2 rounded-full hover:bg-white/10 transition-colors"
onClick={onClose}
whileHover={{ rotate: 90 }}
whileTap={{ scale: 0.9 }}
>
<X className="w-5 h-5 text-white/70" />
</motion.button>
</div>
</div>
{/* 内容区域 */}
<div className="overflow-y-auto p-4" style={{ height: 'calc(100% - 8rem)' }}>
<AnimatePresence mode="wait">
<motion.div
key={`${activeTab}-${resetKey}`}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.2 }}
className="h-full"
>
{renderTabContent()}
</motion.div>
</AnimatePresence>
</div>
{/* 底部操作栏 */}
<div className="p-4 border-t border-white/10 bg-black/20">
<div className="flex justify-end gap-3">
<motion.button
className="px-4 py-2 rounded-lg bg-white/10 text-white hover:bg-white/20 transition-colors"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={handleReset}
>
Reset
</motion.button>
<motion.button
className="px-4 py-2 rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => {handleSave()}}
>
Apply
</motion.button>
</div>
</div>
</motion.div>
</div>
{/* 提醒用户 点击保存 工作流将回退 并重新执行工作流 */}
<FloatingGlassPanel
open={isRemindFallbackOpen}
width='500px'
clickMaskClose={false}
>
<div className="flex flex-col items-center gap-4 text-white py-4">
<div className="flex items-center gap-3">
<TriangleAlert className="w-6 h-6 text-yellow-400" />
<p className="text-lg font-medium">{remindFallbackText}</p>
</div>
<div className="flex gap-3 mt-2">
<button
onClick={() => handleConfirmGotoFallback()}
data-alt="confirm-replace-button"
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors duration-200 flex items-center gap-2"
>
<Undo2 className="w-4 h-4" />
Apply
</button>
<button
onClick={() => handleCloseRemindFallbackPanel()}
data-alt="ignore-button"
className="px-4 py-2 bg-gray-600 hover:bg-gray-700 rounded-md transition-colors duration-200 flex items-center gap-2"
>
<X className="w-4 h-4" />
Cancel
</button>
</div>
</div>
</FloatingGlassPanel>
{/* 提醒用户 重置当前tab修改的数据 */}
<FloatingGlassPanel
open={isRemindResetOpen}
width='500px'
clickMaskClose={false}
>
<div className="flex flex-col items-center gap-4 text-white py-4">
<div className="flex items-center gap-3">
<TriangleAlert className="w-6 h-6 text-yellow-400" />
<p className="text-lg font-medium">Are you sure you want to reset the current tab?</p>
</div>
<div className="flex gap-3 mt-2">
<button
onClick={() => handleConfirmReset()}
data-alt="confirm-replace-button"
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors duration-200 flex items-center gap-2"
>
<Undo2 className="w-4 h-4" />
Reset
</button>
<button
onClick={() => setIsRemindResetOpen(false)}
data-alt="ignore-button"
className="px-4 py-2 bg-gray-600 hover:bg-gray-700 rounded-md transition-colors duration-200 flex items-center gap-2"
>
<X className="w-4 h-4" />
Cancel
</button>
</div>
</div>
</FloatingGlassPanel>
</>
)}
</AnimatePresence>
);
}