11// ============================================
22// ComposeYogi — Export Modal
3- // Progress UI for audio export
3+ // Progress UI for audio export + JSON export
44// ============================================
55
66'use client' ;
77
88import { useEffect , useState , useCallback } from 'react' ;
9- import { Download , CheckCircle , AlertCircle , Loader2 } from 'lucide-react' ;
9+ import { Download , CheckCircle , AlertCircle , Loader2 , FileJson , Music , FileAudio } from 'lucide-react' ;
1010import { Button } from '@/components/ui/button' ;
1111import { Progress } from '@/components/ui/progress' ;
1212import {
@@ -18,6 +18,8 @@ import {
1818} from '@/components/ui/dialog' ;
1919import { useProjectStore } from '@/lib/store' ;
2020import { downloadProjectAsWav } from '@/lib/audio/offline-renderer' ;
21+ import { downloadProjectAsMidi } from '@/lib/audio/export' ;
22+ import { downloadProjectAsJSON } from '@/lib/audio/project-io' ;
2123
2224// ============================================
2325// Types
@@ -39,20 +41,23 @@ export function ExportModal({ isOpen, onClose }: ExportModalProps) {
3941 const [ exportState , setExportState ] = useState < ExportState > ( 'idle' ) ;
4042 const [ progress , setProgress ] = useState ( 0 ) ;
4143 const [ errorMessage , setErrorMessage ] = useState < string | null > ( null ) ;
44+ const [ exportType , setExportType ] = useState < 'wav' | 'midi' | 'json' > ( 'wav' ) ;
4245
4346 // Reset state when modal opens
4447 useEffect ( ( ) => {
4548 if ( isOpen ) {
4649 setExportState ( 'idle' ) ;
4750 setProgress ( 0 ) ;
4851 setErrorMessage ( null ) ;
52+ setExportType ( 'wav' ) ;
4953 }
5054 } , [ isOpen ] ) ;
5155
52- const handleExport = useCallback ( async ( ) => {
56+ const handleExportWav = useCallback ( async ( ) => {
5357 if ( ! project ) return ;
5458
5559 setExportState ( 'exporting' ) ;
60+ setExportType ( 'wav' ) ;
5661 setProgress ( 0 ) ;
5762 setErrorMessage ( null ) ;
5863
@@ -62,7 +67,33 @@ export function ExportModal({ isOpen, onClose }: ExportModalProps) {
6267 } ) ;
6368 setExportState ( 'complete' ) ;
6469 } catch ( error ) {
65- console . error ( '[ExportModal] Export failed:' , error ) ;
70+ console . error ( '[ExportModal] WAV export failed:' , error ) ;
71+ setErrorMessage ( error instanceof Error ? error . message : 'Export failed' ) ;
72+ setExportState ( 'error' ) ;
73+ }
74+ } , [ project ] ) ;
75+
76+ const handleExportMidi = useCallback ( ( ) => {
77+ if ( ! project ) return ;
78+ try {
79+ downloadProjectAsMidi ( project ) ;
80+ setExportState ( 'complete' ) ;
81+ setExportType ( 'midi' ) ;
82+ } catch ( error ) {
83+ console . error ( '[ExportModal] MIDI export failed:' , error ) ;
84+ setErrorMessage ( error instanceof Error ? error . message : 'Export failed' ) ;
85+ setExportState ( 'error' ) ;
86+ }
87+ } , [ project ] ) ;
88+
89+ const handleExportJSON = useCallback ( ( ) => {
90+ if ( ! project ) return ;
91+ try {
92+ downloadProjectAsJSON ( project , undefined , false ) ;
93+ setExportState ( 'complete' ) ;
94+ setExportType ( 'json' ) ;
95+ } catch ( error ) {
96+ console . error ( '[ExportModal] JSON export failed:' , error ) ;
6697 setErrorMessage ( error instanceof Error ? error . message : 'Export failed' ) ;
6798 setExportState ( 'error' ) ;
6899 }
@@ -82,46 +113,69 @@ export function ExportModal({ isOpen, onClose }: ExportModalProps) {
82113 < DialogHeader >
83114 < DialogTitle className = "flex items-center gap-2" >
84115 < Download className = "h-5 w-5" />
85- Export Audio
116+ Export Project
86117 </ DialogTitle >
87118 < DialogDescription >
88- Export "{ project . name } " as a WAV file
119+ Export "{ project . name } " in your preferred format
89120 </ DialogDescription >
90121 </ DialogHeader >
91122
92123 < div className = "py-4 space-y-4" >
93124 { /* Idle State */ }
94125 { exportState === 'idle' && (
95- < div className = "space-y-4" >
96- < div className = "rounded-lg bg-muted/50 p-4 space-y-2" >
97- < div className = "flex justify-between text-sm" >
98- < span className = "text-muted-foreground" > Format</ span >
99- < span className = "font-medium" > WAV (16-bit PCM)</ span >
126+ < div className = "space-y-3" >
127+ { /* WAV Export */ }
128+ < button
129+ onClick = { handleExportWav }
130+ className = "w-full flex items-center gap-4 p-4 rounded-lg border border-border hover:bg-muted/50 transition-colors text-left"
131+ >
132+ < div className = "flex-shrink-0 w-10 h-10 rounded-full bg-green-500/10 flex items-center justify-center" >
133+ < FileAudio className = "h-5 w-5 text-green-500" />
100134 </ div >
101- < div className = "flex justify-between text-sm " >
102- < span className = "text-muted-foreground" > Sample Rate </ span >
103- < span className = "font-medium" > 44.1 kHz</ span >
135+ < div className = "flex-1 min-w-0 " >
136+ < div className = "font-medium" > WAV Audio </ div >
137+ < div className = "text-sm text-muted-foreground" > High-quality 16-bit PCM, 44.1 kHz stereo </ div >
104138 </ div >
105- < div className = "flex justify-between text-sm" >
106- < span className = "text-muted-foreground" > Channels</ span >
107- < span className = "font-medium" > Stereo</ span >
139+ </ button >
140+
141+ { /* MIDI Export */ }
142+ < button
143+ onClick = { handleExportMidi }
144+ className = "w-full flex items-center gap-4 p-4 rounded-lg border border-border hover:bg-muted/50 transition-colors text-left"
145+ >
146+ < div className = "flex-shrink-0 w-10 h-10 rounded-full bg-blue-500/10 flex items-center justify-center" >
147+ < Music className = "h-5 w-5 text-blue-500" />
108148 </ div >
109- </ div >
149+ < div className = "flex-1 min-w-0" >
150+ < div className = "font-medium" > MIDI File</ div >
151+ < div className = "text-sm text-muted-foreground" > Standard MIDI for use in other DAWs</ div >
152+ </ div >
153+ </ button >
154+
155+ { /* JSON Export */ }
156+ < button
157+ onClick = { handleExportJSON }
158+ className = "w-full flex items-center gap-4 p-4 rounded-lg border border-border hover:bg-muted/50 transition-colors text-left"
159+ >
160+ < div className = "flex-shrink-0 w-10 h-10 rounded-full bg-purple-500/10 flex items-center justify-center" >
161+ < FileJson className = "h-5 w-5 text-purple-500" />
162+ </ div >
163+ < div className = "flex-1 min-w-0" >
164+ < div className = "font-medium" > Project File (.cyp)</ div >
165+ < div className = "text-sm text-muted-foreground" > Full project backup, can be re-imported</ div >
166+ </ div >
167+ </ button >
110168
111- < div className = "flex justify-end gap -2" >
169+ < div className = "flex justify-end pt -2" >
112170 < Button variant = "outline" onClick = { handleClose } >
113171 Cancel
114172 </ Button >
115- < Button onClick = { handleExport } >
116- < Download className = "mr-2 h-4 w-4" />
117- Export WAV
118- </ Button >
119173 </ div >
120174 </ div >
121175 ) }
122176
123- { /* Exporting State */ }
124- { exportState === 'exporting' && (
177+ { /* Exporting State (WAV only) */ }
178+ { exportState === 'exporting' && exportType === 'wav' && (
125179 < div className = "space-y-4" >
126180 < div className = "flex items-center gap-3" >
127181 < Loader2 className = "h-5 w-5 animate-spin text-accent" />
@@ -177,7 +231,7 @@ export function ExportModal({ isOpen, onClose }: ExportModalProps) {
177231 < Button variant = "outline" onClick = { handleClose } >
178232 Close
179233 </ Button >
180- < Button onClick = { handleExport } >
234+ < Button onClick = { ( ) => setExportState ( 'idle' ) } >
181235 Try Again
182236 </ Button >
183237 </ div >
0 commit comments