import React, { useState, useMemo, useCallback, useRef } from 'react'; import { ArrowLeft, Zap, Wrench, Download, ChevronRight, Play, Pause, RotateCcw } from 'lucide-react'; import { BradlyPlayer, BradlyPlayerRef } from '../../engine/player'; import { ExpressTemplate, DesignMD, TimelineElement, TimelineLayer, CompanyProfile } from '../../types'; import { BrandComposition } from '../BrandComposition'; import { ExpressTemplateGallery } from './ExpressTemplateGallery'; import { StoryboardView } from './StoryboardView'; import { SceneFieldEditor } from './SceneFieldEditor'; import { ExpressStylePanel } from './ExpressStylePanel'; import { compileExpressToTimeline, getAspectDimensions, getTemplateDuration } from '../../utils/expressCompiler'; import { useVideoDurations } from '../../hooks/useVideoDurations'; interface ExpressEditorProps { designMD: DesignMD; company?: CompanyProfile; onBack: () => void; onUpgradeToPro: (elements: TimelineElement[], layers: TimelineLayer[]) => void; onExport: (elements: TimelineElement[], layers: TimelineLayer[], format: 'video' | 'image') => void; } type EditorPhase = 'gallery' | 'editing'; /** * ExpressEditor — Scene-based storyboard editor. * No video editor, no timeline, no toolbar. * User picks a template → fills in scenes → exports. */ export const ExpressEditor: React.FC = ({ designMD, company, onBack, onUpgradeToPro, onExport, }) => { const [phase, setPhase] = useState('gallery'); const [selectedTemplate, setSelectedTemplate] = useState(null); const [fieldData, setFieldData] = useState>({}); const [activeSceneId, setActiveSceneId] = useState(null); const [isPlaying, setIsPlaying] = useState(false); // Style options const [bgStyle, setBgStyle] = useState<'solid' | 'gradient' | 'dark'>('gradient'); const [showLogo, setShowLogo] = useState(true); const [overlayOpacity, setOverlayOpacity] = useState(0); const playerRef = useRef(null); const handleSelectTemplate = useCallback((template: ExpressTemplate) => { setSelectedTemplate(template); // Pre-fill field data with empty strings const initial: Record = {}; template.scenes.forEach(scene => { scene.editableFields.forEach(field => { initial[field.id] = ''; }); }); setFieldData(initial); setActiveSceneId(template.scenes[0]?.id || null); setPhase('editing'); }, []); const handleFieldChange = useCallback((fieldId: string, value: string) => { setFieldData(prev => ({ ...prev, [fieldId]: value })); }, []); // Probe actual video durations for form-sourced scenes const videoDurations = useVideoDurations(selectedTemplate, fieldData); // Compile template to timeline const compiled = useMemo(() => { if (!selectedTemplate) return null; return compileExpressToTimeline(selectedTemplate, fieldData, designMD, company, videoDurations); }, [selectedTemplate, fieldData, designMD, company, videoDurations]); const totalDuration = selectedTemplate ? getTemplateDuration(selectedTemplate, videoDurations) : 0; const fps = 30; const totalFrames = Math.max(30, totalDuration * fps); const dimensions = selectedTemplate ? getAspectDimensions(selectedTemplate.aspectRatio) : { w: 1080, h: 1920 }; const activeScene = selectedTemplate?.scenes.find(s => s.id === activeSceneId) || null; const handlePlayToggle = useCallback(() => { if (playerRef.current) { if (isPlaying) { playerRef.current.pause(); } else { playerRef.current.play(); } setIsPlaying(!isPlaying); } }, [isPlaying]); const handleUpgrade = () => { if (compiled) onUpgradeToPro(compiled.elements, compiled.layers); }; const handleExport = () => { if (compiled && selectedTemplate) { onExport(compiled.elements, compiled.layers, selectedTemplate.format); } }; // Navigate to scene in player const handleSelectScene = useCallback((sceneId: string) => { setActiveSceneId(sceneId); if (!selectedTemplate || !playerRef.current) return; // Seek player to scene start let frameOffset = 0; for (const scene of selectedTemplate.scenes) { if (scene.id === sceneId) break; frameOffset += scene.durationSeconds * fps; } playerRef.current.seekTo(frameOffset); playerRef.current.pause(); setIsPlaying(false); }, [selectedTemplate, fps]); const bgColor = bgStyle === 'dark' ? '#111111' : bgStyle === 'gradient' ? undefined : designMD.secondaryColor; return (
{/* ═══ Top Bar ═══ */}
EXPRESS
{phase === 'editing' && ( <> )}
{/* ═══ Content ═══ */} {phase === 'gallery' ? ( ) : selectedTemplate && compiled ? (
{/* Main area: Preview + Right Panel */}
{/* Canvas Area */}
{/* Subtle pattern */}
{/* Template name */}
{selectedTemplate.icon} {selectedTemplate.name} {selectedTemplate.aspectRatio} {totalDuration}s
{/* Player */}
{overlayOpacity > 0 && (
)}
{/* Mini play controls */} {selectedTemplate.format === 'video' && (
{totalDuration}s
)}
{/* Right Panel — Scene Fields */}
{/* Storyboard (bottom strip — video only) */} {selectedTemplate.format === 'video' && ( )}
) : null}
); };