forked from 77media/video-flow
282 lines
12 KiB
TypeScript
282 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { X, Upload, Library, Wand2, Search, FileVideo, Plus, ChevronDown } from 'lucide-react';
|
|
import { cn } from '@/public/lib/utils';
|
|
import { GenerateVideoModal } from './generate-video-modal';
|
|
|
|
interface ReplaceVideoModalProps {
|
|
isOpen: boolean;
|
|
activeReplaceMethod: string;
|
|
onClose: () => void;
|
|
onVideoSelect: (video: any) => void;
|
|
}
|
|
|
|
export function ReplaceVideoModal({
|
|
isOpen,
|
|
activeReplaceMethod,
|
|
onClose,
|
|
onVideoSelect
|
|
}: ReplaceVideoModalProps) {
|
|
const [activeMethod, setActiveMethod] = useState(activeReplaceMethod);
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [isGenerateModalOpen, setIsGenerateModalOpen] = React.useState(false);
|
|
|
|
useEffect(() => {
|
|
setActiveMethod(activeReplaceMethod);
|
|
}, [activeReplaceMethod]);
|
|
|
|
// 模拟素材库视频数据
|
|
const mockLibraryVideos = [
|
|
{ id: 1, url: 'https://example.com/video1.mp4', title: '烟花绽放', duration: '00:15' },
|
|
{ id: 2, url: 'https://example.com/video2.mp4', title: '城市夜景', duration: '00:20' },
|
|
{ id: 3, url: 'https://example.com/video3.mp4', title: '海浪声音', duration: '00:30' },
|
|
];
|
|
|
|
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0];
|
|
if (file) {
|
|
// 处理文件上传
|
|
console.log('Uploading file:', file);
|
|
}
|
|
};
|
|
|
|
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="w-[66%] min-w-[800px] 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 justify-between p-4 border-b border-white/10">
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
className="p-1 hover:bg-white/10 rounded-full transition-colors"
|
|
onClick={onClose}
|
|
>
|
|
<ChevronDown className="w-5 h-5" />
|
|
</button>
|
|
<h2 className="text-lg font-medium">视频替换替换</h2>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 主要内容区域 */}
|
|
<div className="p-6 space-y-6 h-[80vh]">
|
|
{/* 替换方式选择 */}
|
|
<div className="flex gap-4">
|
|
<motion.button
|
|
className={cn(
|
|
'flex-1 flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors',
|
|
activeMethod === 'upload'
|
|
? 'border-blue-500 bg-blue-500/10'
|
|
: 'border-white/10 hover:border-white/20'
|
|
)}
|
|
onClick={() => setActiveMethod('upload')}
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
<Upload className="w-6 h-6" />
|
|
<span>上传视频</span>
|
|
</motion.button>
|
|
|
|
<motion.button
|
|
className={cn(
|
|
'flex-1 flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors',
|
|
activeMethod === 'library'
|
|
? 'border-blue-500 bg-blue-500/10'
|
|
: 'border-white/10 hover:border-white/20'
|
|
)}
|
|
onClick={() => setActiveMethod('library')}
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
<Library className="w-6 h-6" />
|
|
<span>素材库</span>
|
|
</motion.button>
|
|
|
|
<motion.button
|
|
className={cn(
|
|
'flex-1 flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors',
|
|
activeMethod === 'generate'
|
|
? 'border-blue-500 bg-blue-500/10'
|
|
: 'border-white/10 hover:border-white/20'
|
|
)}
|
|
onClick={() => setActiveMethod('generate')}
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
<Wand2 className="w-6 h-6" />
|
|
<span>生成视频</span>
|
|
</motion.button>
|
|
</div>
|
|
|
|
{/* 内容区域 */}
|
|
<AnimatePresence mode="wait">
|
|
{activeMethod === 'upload' && (
|
|
<motion.div
|
|
key="upload"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
className="flex flex-col items-center justify-center p-8 border-2 border-dashed border-white/10 rounded-lg"
|
|
>
|
|
<input
|
|
type="file"
|
|
accept="video/*"
|
|
className="hidden"
|
|
id="video-upload"
|
|
onChange={handleFileUpload}
|
|
/>
|
|
<label
|
|
htmlFor="video-upload"
|
|
className="flex flex-col items-center gap-4 cursor-pointer"
|
|
>
|
|
<motion.div
|
|
className="p-4 rounded-full bg-white/5"
|
|
whileHover={{ scale: 1.1, backgroundColor: 'rgba(255,255,255,0.1)' }}
|
|
whileTap={{ scale: 0.9 }}
|
|
>
|
|
<FileVideo className="w-8 h-8 text-white/70" />
|
|
</motion.div>
|
|
<div className="text-center">
|
|
<p className="text-white/70">点击上传或拖拽视频文件到此处</p>
|
|
<p className="text-sm text-white/50 mt-2">支持 MP4, MOV, AVI 格式</p>
|
|
</div>
|
|
</label>
|
|
</motion.div>
|
|
)}
|
|
|
|
{activeMethod === 'library' && (
|
|
<motion.div
|
|
key="library"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
className="space-y-4"
|
|
>
|
|
{/* 搜索框 */}
|
|
<div className="relative">
|
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-white/50" />
|
|
<input
|
|
type="text"
|
|
placeholder="搜索视频..."
|
|
className="w-full pl-10 pr-4 py-2 bg-white/5 border border-white/10 rounded-lg
|
|
focus:outline-none focus:border-blue-500 transition-colors"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{/* 视频列表 */}
|
|
<div className="grid grid-cols-3 gap-4">
|
|
{mockLibraryVideos.map((video) => (
|
|
<motion.div
|
|
key={video.id}
|
|
className="group relative aspect-video rounded-lg overflow-hidden cursor-pointer"
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
onClick={() => onVideoSelect(video)}
|
|
>
|
|
<video
|
|
src={video.url}
|
|
className="w-full h-full object-cover"
|
|
muted
|
|
loop
|
|
playsInline
|
|
onMouseEnter={(e) => e.currentTarget.play()}
|
|
onMouseLeave={(e) => e.currentTarget.pause()}
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent
|
|
opacity-0 group-hover:opacity-100 transition-opacity">
|
|
<div className="absolute bottom-0 left-0 right-0 p-2">
|
|
<p className="text-sm font-medium">{video.title}</p>
|
|
<p className="text-xs text-white/70">{video.duration}</p>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
|
|
{activeMethod === 'generate' && (
|
|
<motion.div
|
|
key="generate"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
className="flex flex-col items-center gap-4"
|
|
>
|
|
<motion.button
|
|
className="flex items-start gap-2 px-6 py-3 bg-blue-500 hover:bg-blue-600
|
|
rounded-lg transition-colors"
|
|
onClick={() => setIsGenerateModalOpen(true)}
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
<Plus className="w-5 h-5" />
|
|
<span>生成新视频</span>
|
|
</motion.button>
|
|
</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}
|
|
>
|
|
重置
|
|
</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 }}
|
|
>
|
|
应用
|
|
</motion.button>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
|
|
{/* 生成视频弹窗 */}
|
|
<GenerateVideoModal
|
|
isOpen={isGenerateModalOpen}
|
|
onClose={() => setIsGenerateModalOpen(false)}
|
|
onGenerate={(params) => {
|
|
console.log('Generate video with params:', params);
|
|
setIsGenerateModalOpen(false);
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|