"use client"; import { useState } from "react"; import { TransitionUpIcon } from "../icons"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { Button } from "../ui/button"; import { Label } from "../ui/label"; import { RadioGroup, RadioGroupItem } from "../ui/radio-group"; import { Progress } from "../ui/progress"; import { Checkbox } from "../ui/checkbox"; import { cn } from "@/lib/utils"; import { exportProject, getExportMimeType, getExportFileExtension, DEFAULT_EXPORT_OPTIONS, } from "@/lib/export-utils"; import { useProjectStore } from "@/stores/project-store"; import { Check, Copy, Download, RotateCcw, X } from "lucide-react"; import { ExportFormat, ExportQuality, ExportResult } from "@/types/export"; import { PropertyGroup } from "./properties-panel/property-item"; export function ExportButton() { const [isExportPopoverOpen, setIsExportPopoverOpen] = useState(false); const { activeProject } = useProjectStore(); const handleExport = () => { setIsExportPopoverOpen(true); }; const hasProject = !!activeProject; return ( { if (hasProject && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); handleExport(); } }} > Export {hasProject && } ); } function ExportPopover({ onOpenChange, }: { onOpenChange: (open: boolean) => void; }) { const { activeProject } = useProjectStore(); const [format, setFormat] = useState( DEFAULT_EXPORT_OPTIONS.format ); const [quality, setQuality] = useState( DEFAULT_EXPORT_OPTIONS.quality ); const [includeAudio, setIncludeAudio] = useState( DEFAULT_EXPORT_OPTIONS.includeAudio || true ); const [isExporting, setIsExporting] = useState(false); const [progress, setProgress] = useState(0); const [exportResult, setExportResult] = useState(null); const handleExport = async () => { if (!activeProject) return; setIsExporting(true); setProgress(0); setExportResult(null); const result = await exportProject({ format, quality, fps: activeProject.fps, includeAudio, onProgress: setProgress, onCancel: () => false, // TODO: Add cancel functionality }); setIsExporting(false); setExportResult(result); if (result.success && result.buffer) { // Download the file const mimeType = getExportMimeType(format); const extension = getExportFileExtension(format); const blob = new Blob([result.buffer], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${activeProject.name}${extension}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); onOpenChange(false); setExportResult(null); setProgress(0); } }; const handleClose = () => { if (!isExporting) { onOpenChange(false); setExportResult(null); setProgress(0); } }; return ( <> {exportResult && !exportResult.success ? ( ) : ( <> {isExporting ? "Exporting project" : "Export project"} {!isExporting && ( <> setFormat(value as ExportFormat) } > MP4 (H.264) - Better compatibility WebM (VP9) - Smaller file size setQuality(value as ExportQuality) } > Low - Smallest file size Medium - Balanced High - Recommended Very High - Largest file size setIncludeAudio(!!checked) } /> Include audio in export Export > )} {isExporting && ( {Math.round(progress * 100)}% 100% {}} > Cancel )} > )} > ); } function ExportError({ error, onRetry, }: { error: string; onRetry: () => void; }) { const [copied, setCopied] = useState(false); const handleCopy = async () => { await navigator.clipboard.writeText(error); setCopied(true); setTimeout(() => setCopied(false), 1000); }; return ( Export failed {error} {copied ? : } Copy Retry ); }
{Math.round(progress * 100)}%
100%
Export failed
{error}