"use client"; import { useState, useRef } from "react"; import { TransitionTopIcon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Progress } from "@/components/ui/progress"; import { Checkbox } from "@/components/ui/checkbox"; import { cn } from "@/utils/ui"; import { getExportMimeType, getExportFileExtension } from "@/lib/export"; import { Check, Copy, Download, RotateCcw } from "lucide-react"; import { EXPORT_FORMAT_VALUES, EXPORT_QUALITY_VALUES, type ExportFormat, type ExportQuality, type ExportResult, } from "@/types/export"; import { Section, SectionContent, SectionHeader, } from "@/components/editor/panels/properties/section"; import { useEditor } from "@/hooks/use-editor"; import { DEFAULT_EXPORT_OPTIONS } from "@/constants/export-constants"; export function ExportButton() { const [isExportPopoverOpen, setIsExportPopoverOpen] = useState(false); const editor = useEditor(); const handleExport = () => { setIsExportPopoverOpen(true); }; const hasProject = !!editor.project.getActive(); return ( { if (hasProject && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); handleExport(); } }} > Export {hasProject && } ); } function ExportPopover({ onOpenChange, }: { onOpenChange: (open: boolean) => void; }) { const editor = useEditor(); const activeProject = editor.project.getActive(); 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 cancelRequestedRef = useRef(false); const handleExport = async () => { if (!activeProject) return; cancelRequestedRef.current = false; setIsExporting(true); setProgress(0); setExportResult(null); const result = await editor.project.export({ options: { format, quality, fps: activeProject.settings.fps, includeAudio, onProgress: ({ progress }) => setProgress(progress), onCancel: () => cancelRequestedRef.current, }, }); setIsExporting(false); if (result.cancelled) { setExportResult(null); setProgress(0); return; } setExportResult(result); if (result.success && result.buffer) { 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.metadata.name}${extension}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); onOpenChange(false); setExportResult(null); setProgress(0); } }; const handleCancel = () => { cancelRequestedRef.current = true; }; return ( {exportResult && !exportResult.success ? ( ) : ( <> {isExporting ? "Exporting project" : "Export project"} {!isExporting && ( <> { if (isExportFormat(value)) { setFormat(value); } }} > MP4 (H.264) - Better compatibility WebM (VP9) - Smaller file size { if (isExportQuality(value)) { setQuality(value); } }} > 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 isExportFormat(value: string): value is ExportFormat { return EXPORT_FORMAT_VALUES.some((formatValue) => formatValue === value); } function isExportQuality(value: string): value is ExportQuality { return EXPORT_QUALITY_VALUES.some((qualityValue) => qualityValue === value); } 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}