import React, { useState, useEffect, useRef } from 'react'; import { Plus, Trash2, Edit3, Download, Play, Pause, ChevronDown, Settings, Save, X, FileText, UploadCloud, RefreshCw, Volume2, Clock, FileVideo, Check, AlertTriangle, Zap, Layers, Grid, ArrowLeft, ArrowRight, Move, Eye } from 'lucide-react'; // Shadcn UI components import { Alert, AlertDescription } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; import { Switch } from '@/components/ui/switch'; import { Progress } from '@/components/ui/progress'; import { Checkbox } from '@/components/ui/checkbox'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { Textarea } from '@/components/ui/textarea'; const VideoGeneratorApp = () => { // State management const [activeTab, setActiveTab] = useState("blocks"); const [currentBlock, setCurrentBlock] = useState(null); const [isPlaying, setIsPlaying] = useState(false); const [progress, setProgress] = useState(0); const [showEditor, setShowEditor] = useState(false); const [targetLanguage, setTargetLanguage] = useState("es-ES"); const [nativeLanguage, setNativeLanguage] = useState("en-US"); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(15); const [editingElement, setEditingElement] = useState(null); const timelineRef = useRef(null); const previewRef = useRef(null); // Project data const [projectName, setProjectName] = useState("Vocabulary Project"); const [showNotification, setShowNotification] = useState(false); const [notificationMessage, setNotificationMessage] = useState(""); const [generatingStatus, setGeneratingStatus] = useState(null); const [renderQueue, setRenderQueue] = useState([]); const [selectedAnimationStyle, setSelectedAnimationStyle] = useState("fadeIn"); const [audioSyncEnabled, setAudioSyncEnabled] = useState(true); // Content data const [content, setContent] = useState([ { id: 1, targetWord: "médico", nativeWord: "doctor", targetPhrase1: "quiere ser médico", nativePhrase1: "he wants to be a doctor", targetPhrase2: "su médico es bueno", nativePhrase2: "his doctor is good", }, { id: 2, targetWord: "abogado", nativeWord: "lawyer", targetPhrase1: "estudia para abogado", nativePhrase1: "he studies to be a lawyer", targetPhrase2: "necesita un abogado", nativePhrase2: "he needs a lawyer", } ]); const [currentContentIndex, setCurrentContentIndex] = useState(0); const [newRow, setNewRow] = useState({ targetWord: "", nativeWord: "", targetPhrase1: "", nativePhrase1: "", targetPhrase2: "", nativePhrase2: "" }); // Blocks const [blocks, setBlocks] = useState([ { id: "vocab-block", name: "Vocabulary Block", description: "Standard vocabulary block with word and phrases", duration: 15, background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)" }, { id: "grammar-block", name: "Grammar Block", description: "Block for grammar explanations with examples", duration: 20, background: "linear-gradient(135deg, #16213e 0%, #0f3460 100%)" }, { id: "conversation-block", name: "Conversation Block", description: "Dialog-based block for conversation practice", duration: 30, background: "linear-gradient(135deg, #0f3460 0%, #950740 100%)" } ]); // Timeline elements const [timelineElements, setTimelineElements] = useState([ { id: "el-1", type: "text", content: "Vocabulary Word", start: 0, duration: 3, position: {x: 50, y: 20}, style: {color: "#ffffff", fontSize: 36, fontWeight: "bold"}, animation: "fadeIn", contentKey: "targetWord" }, { id: "el-2", type: "block", content: "Color Block", start: 0.5, duration: 12, position: {x: 30, y: 35}, style: {width: 200, height: 200, borderRadius: "8px", background: "linear-gradient(45deg, #3490dc, #6574cd)"}, animation: "zoomIn" }, { id: "el-3", type: "text", content: "Example Phrase 1", start: 4, duration: 3, position: {x: 50, y: 65}, style: {color: "#ffffff", fontSize: 24}, animation: "slideIn", contentKey: "targetPhrase1" }, { id: "el-4", type: "text", content: "Example Phrase 2", start: 8, duration: 3, position: {x: 50, y: 80}, style: {color: "#ffffff", fontSize: 24}, animation: "slideIn", contentKey: "targetPhrase2" }, { id: "el-5", type: "audio", content: "narration.mp3", start: 0, duration: 15 }, ]); // Notification helper const showNotify = (message) => { setNotificationMessage(message); setShowNotification(true); setTimeout(() => setShowNotification(false), 3000); }; // Block selection const selectBlock = (blockId) => { setCurrentBlock(blocks.find(b => b.id === blockId)); showNotify("Block selected: " + blocks.find(b => b.id === blockId).name); setActiveTab("content"); }; // Add new content row const addContentRow = () => { if (!newRow.targetWord || !newRow.nativeWord) { showNotify("Target and native words are required"); return; } setContent([...content, { id: content.length + 1, ...newRow }]); setNewRow({ targetWord: "", nativeWord: "", targetPhrase1: "", nativePhrase1: "", targetPhrase2: "", nativePhrase2: "" }); showNotify("New content row added"); }; // Delete content row const deleteContentRow = (id) => { setContent(content.filter(row => row.id !== id)); showNotify("Content row deleted"); }; // Toggle play/pause const togglePlayback = () => { setIsPlaying(!isPlaying); }; // Animation preview useEffect(() => { let interval; if (isPlaying) { interval = setInterval(() => { setCurrentTime(time => { const newTime = time + 0.1; if (newTime >= duration) { setIsPlaying(false); return 0; } return newTime; }); setProgress(prog => { const newProg = prog + (100 / (duration * 10)); return newProg > 100 ? 0 : newProg; }); }, 100); } return () => clearInterval(interval); }, [isPlaying, duration]); // Handle timeline scrubbing const handleTimelineClick = (e) => { if (!timelineRef.current) return; const rect = timelineRef.current.getBoundingClientRect(); const clickPosition = e.clientX - rect.left; const percentClicked = clickPosition / rect.width; const newTime = percentClicked * duration; setCurrentTime(Math.max(0, Math.min(newTime, duration))); setProgress(percentClicked * 100); }; // Open element editor const openElementEditor = (element) => { setEditingElement(element); setShowEditor(true); }; // Update element const updateElement = (updatedElement) => { setTimelineElements(elements => elements.map(el => el.id === updatedElement.id ? updatedElement : el) ); setShowEditor(false); showNotify("Element updated"); }; // Add new element const addTimelineElement = (type) => { const newElement = { id: `el-${Date.now()}`, type, content: type === 'text' ? 'New Text' : type === 'block' ? 'Color Block' : 'audio.mp3', start: currentTime, duration: type === 'audio' ? 5 : 3, position: type !== 'audio' ? {x: 50, y: 50} : undefined, style: type === 'text' ? {color: "#ffffff", fontSize: 24} : type === 'block' ? { width: 150, height: 150, borderRadius: "8px", background: "linear-gradient(45deg, #3490dc, #6574cd)" } : undefined, animation: type !== 'audio' ? selectedAnimationStyle : undefined }; setTimelineElements([...timelineElements, newElement]); setEditingElement(newElement); setShowEditor(true); showNotify(`New ${type} element added`); }; // Generate audio const generateAudio = () => { showNotify("Generating audio for content..."); // Simulate audio generation setTimeout(() => { showNotify("Audio generation complete!"); }, 1500); }; // Generate videos for all content const generateAllVideos = () => { if (!currentBlock) { showNotify("Please select a block first"); return; } if (content.length === 0) { showNotify("No content to generate"); return; } // Create render queue const queue = content.map(item => ({ id: `render-${item.id}`, content: item, block: currentBlock, status: "pending", progress: 0 })); setRenderQueue(queue); setGeneratingStatus("preparing"); // Simulate processing setTimeout(() => { setGeneratingStatus("processing"); let currentIndex = 0; const processNext = () => { if (currentIndex >= queue.length) { setGeneratingStatus("complete"); return; } const updatedQueue = [...queue]; updatedQueue[currentIndex].status = "processing"; setRenderQueue(updatedQueue); // Simulate processing time let progress = 0; const interval = setInterval(() => { progress += 10; const newQueue = [...updatedQueue]; newQueue[currentIndex].progress = progress; setRenderQueue(newQueue); if (progress >= 100) { clearInterval(interval); newQueue[currentIndex].status = "complete"; setRenderQueue(newQueue); currentIndex++; setTimeout(processNext, 500); } }, 300); }; processNext(); }, 1500); }; // Reset progress for new render const startNewProject = () => { setCurrentBlock(null); setActiveTab("blocks"); setProjectName("New Project"); setContent([]); setRenderQueue([]); setGeneratingStatus(null); showNotify("Started new project"); }; // Get element animation state const getElementAnimationState = (element) => { if (currentTime < element.start) { return "before"; } else if (currentTime >= element.start && currentTime < element.start + element.duration) { return "active"; } else { return "after"; } }; // Get real content based on timeline element const getElementContent = (element) => { if (!element.contentKey || currentContentIndex >= content.length) { return element.content; } const contentItem = content[currentContentIndex]; return contentItem[element.contentKey] || element.content; }; return (
{block.description}
No videos in queue yet
Click the "Generate All Videos" button to start generation