"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 "@/export"; import { Check, Copy, Download, RotateCcw } from "lucide-react"; import { EXPORT_FORMAT_VALUES, EXPORT_QUALITY_VALUES, type ExportFormat, type ExportQuality, } from "@/export"; import { Section, SectionContent, SectionHeader, SectionTitle, } from "@/components/section"; import { useEditor } from "@/editor/use-editor"; import { DEFAULT_EXPORT_OPTIONS } from "@/export/defaults"; import { getDesktopBridge } from "@/desktop/bridge"; import { toast } from "sonner"; 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 activeProject = useEditor((e) => e.project.getActiveOrNull()); const hasProject = !!activeProject; const handlePopoverOpenChange = ({ open }: { open: boolean }) => { if (!open) { editor.project.cancelExport(); editor.project.clearExportState(); } setIsExportPopoverOpen(open); }; return ( handlePopoverOpenChange({ open })} > setIsExportPopoverOpen(true) : undefined} disabled={!hasProject} onKeyDown={(event) => { if (hasProject && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); setIsExportPopoverOpen(true); } }} > Export {hasProject && } ); } function ExportPopover({ onOpenChange, }: { onOpenChange: (open: boolean) => void; }) { const editor = useEditor(); const activeProject = useEditor((e) => e.project.getActive()); const exportState = useEditor((e) => e.project.getExportState()); const { isExporting, progress, result: exportResult } = exportState; 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; } const filePath = result.success ? result.filePath : undefined; if (result.success && filePath) { toast.success(`Export saved to ${filePath}`, { action: { label: "Reveal", onClick: () => { getDesktopBridge()?.revealFile(filePath); }, }, }); editor.project.clearExportState(); onOpenChange(false); 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); } }} > MP4 (H.264) - Better compatibility WebM (VP9) - Smaller file size Quality { if (isExportQuality(value)) { setQuality(value); } }} > Low - Smallest file size Medium - Balanced High - Recommended Very high - Largest file size Audio setShouldIncludeAudio(!!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}