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

327 lines
11 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 } 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;
setAnyAttribute: any;
isPauseWorkFlow: boolean;
scriptData: any[] | null;
applyScript: any;
}
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,
setAnyAttribute,
isPauseWorkFlow,
scriptData,
applyScript
}: EditModalProps) {
const [activeTab, setActiveTab] = useState(activeEditTab);
const [currentIndex, setCurrentIndex] = useState(currentSketchIndex);
const [currentRoleIndex, setCurrentRoleIndex] = useState(0);
const [isRemindFallbackOpen, setIsRemindFallbackOpen] = useState(false);
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 handleChangeTab = (tabId: string, disabled: boolean) => {
if (disabled) return;
setActiveTab(tabId);
setCurrentIndex(0);
}
const handleSave = () => {
console.log('handleSave');
setIsRemindFallbackOpen(true);
}
const handleConfirmGotoFallback = () => {
console.log('handleConfirmGotoFallback');
}
const handleCloseRemindFallbackPanel = () => {
setIsRemindFallbackOpen(false);
}
const renderTabContent = () => {
switch (activeTab) {
case '0':
return (
<ScriptTabContent
scriptData={scriptData}
setIsPauseWorkFlow={setIsPauseWorkFlow}
setAnyAttribute={setAnyAttribute}
isPauseWorkFlow={isPauseWorkFlow}
applyScript={applyScript}
/>
);
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}
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={onClose}
>
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()}}
>
Save
</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"></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" />
</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" />
</button>
</div>
</div>
</FloatingGlassPanel>
</>
)}
</AnimatePresence>
);
}