"use client"; import { useState } 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, downloadBuffer } from "@/lib/export"; import { Check, Copy, Download, RotateCcw } from "lucide-react"; import { EXPORT_FORMAT_VALUES, EXPORT_QUALITY_VALUES, type ExportFormat, type ExportQuality, } from "@/types/export"; import { Section, SectionContent, SectionHeader, SectionTitle, } from "@/components/editor/panels/properties/section"; import { useEditor } from "@/hooks/use-editor"; import { DEFAULT_EXPORT_OPTIONS } from "@/constants/export-constants"; 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); } export function ExportButton() { const [isExportPopoverOpen, setIsExportPopoverOpen] = useState(false); const editor = useEditor(); const hasProject = !!editor.project.getActiveOrNull(); const handlePopoverOpenChange = ({ open }: { open: boolean }) => { if (!open) { editor.project.cancelExport(); editor.project.clearExportState(); } setIsExportPopoverOpen(open); }; return ( handlePopoverOpenChange({ open })}> {hasProject && } ); } function ExportPopover({ onOpenChange, }: { onOpenChange: (open: boolean) => void; }) { const editor = useEditor(); const activeProject = editor.project.getActive(); const { isExporting, progress, result: exportResult } = editor.project.getExportState(); const [format, setFormat] = useState( DEFAULT_EXPORT_OPTIONS.format, ); const [quality, setQuality] = useState( DEFAULT_EXPORT_OPTIONS.quality, ); const [shouldIncludeAudio, setShouldIncludeAudio] = useState( DEFAULT_EXPORT_OPTIONS.includeAudio ?? true, ); const handleExport = async () => { if (!activeProject) return; const result = await editor.project.export({ options: { format, quality, fps: activeProject.settings.fps, includeAudio: shouldIncludeAudio, }, }); if (result.cancelled) { editor.project.clearExportState(); return; } if (result.success && result.buffer) { downloadBuffer({ buffer: result.buffer, filename: `${activeProject.metadata.name}${getExportFileExtension({ format })}`, mimeType: getExportMimeType({ format }), }); editor.project.clearExportState(); onOpenChange(false); } }; const handleCancel = () => { editor.project.cancelExport(); }; return ( {exportResult && !exportResult.success ? ( ) : ( <>

{isExporting ? "Exporting project" : "Export project"}

{!isExporting && ( <>
Format { if (isExportFormat(value)) { setFormat(value); } }} >
Quality { if (isExportQuality(value)) { setQuality(value); } }} >
Audio
setShouldIncludeAudio(!!checked) } />
)} {isExporting && (

{Math.round(progress * 100)}%

100%

)}
)}
); } 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}

); }