@@ -23,6 +23,14 @@ function nextSoundNumberAfter(soundEntries) {
2323 return highest + 1 ;
2424}
2525
26+ function exportFileName ( name ) {
27+ const baseName = name
28+ . toLowerCase ( )
29+ . replace ( / [ ^ a - z 0 - 9 ] + / g, "-" )
30+ . replace ( / ^ - + | - + $ / g, "" ) ;
31+ return `${ baseName || "audio-sfx" } -tool-state.json` ;
32+ }
33+
2634export class AudioSfxPlaygroundV2App {
2735 constructor ( { accordions, actionNav, audioEngine, controls, inspector, preview, serializer, shell, statusLog, tileList, windowRef = window } ) {
2836 this . accordions = accordions ;
@@ -180,15 +188,39 @@ export class AudioSfxPlaygroundV2App {
180188 this . statusLog . write ( `Deleted ${ entry . sound . name } .` ) ;
181189 }
182190
183- exportJson ( ) {
191+ exportPayload ( ) {
184192 const { toolState, validation } = this . currentToolState ( ) ;
185193 if ( ! validation . valid ) {
186- this . statusLog . error ( validation . message ) ;
194+ return { valid : false , message : validation . message , toolState : null , json : "" } ;
195+ }
196+
197+ const exportValidation = this . serializer . readToolState ( toolState ) ;
198+ if ( ! exportValidation . valid ) {
199+ return { valid : false , message : exportValidation . message , toolState : null , json : "" } ;
200+ }
201+ return {
202+ valid : true ,
203+ message : "" ,
204+ toolState,
205+ json : JSON . stringify ( toolState , null , 2 )
206+ } ;
207+ }
208+
209+ exportJson ( ) {
210+ const exportResult = this . exportPayload ( ) ;
211+ if ( ! exportResult . valid ) {
212+ this . statusLog . error ( `Export JSON failed: ${ exportResult . message } ` ) ;
187213 this . refreshPreview ( ) ;
188214 return ;
189215 }
190- this . inspector . showObject ( toolState ) ;
191- this . statusLog . write ( "JSON preview written to Output Summary." ) ;
216+
217+ try {
218+ this . downloadJson ( exportResult ) ;
219+ this . inspector . showObject ( exportResult . toolState ) ;
220+ this . statusLog . write ( `Exported JSON for ${ exportResult . toolState . payload . name } .` ) ;
221+ } catch ( error ) {
222+ this . statusLog . error ( `Export JSON failed: ${ error . message } ` ) ;
223+ }
192224 }
193225
194226 async importJson ( file ) {
@@ -200,12 +232,13 @@ export class AudioSfxPlaygroundV2App {
200232 this . statusLog . error ( `Import JSON failed: ${ result . message } ` ) ;
201233 return ;
202234 }
203- this . activeSoundId = result . value . activeSoundId ;
204- this . soundEntries = result . value . soundEntries . map ( ( entry ) => ( {
235+ const nextSoundEntries = result . value . soundEntries . map ( ( entry ) => ( {
205236 id : entry . id ,
206237 sound : cloneSound ( entry . sound )
207238 } ) ) ;
208- this . nextSoundNumber = nextSoundNumberAfter ( this . soundEntries ) ;
239+ this . activeSoundId = result . value . activeSoundId ;
240+ this . soundEntries = nextSoundEntries ;
241+ this . nextSoundNumber = nextSoundNumberAfter ( nextSoundEntries ) ;
209242 this . controls . loadSound ( result . value . sound ) ;
210243 this . renderSoundList ( ) ;
211244 this . refreshPreview ( ) ;
@@ -216,25 +249,46 @@ export class AudioSfxPlaygroundV2App {
216249 }
217250
218251 async copyJson ( ) {
219- const { toolState , validation } = this . currentToolState ( ) ;
220- if ( ! validation . valid ) {
221- this . statusLog . error ( validation . message ) ;
252+ const exportResult = this . exportPayload ( ) ;
253+ if ( ! exportResult . valid ) {
254+ this . statusLog . error ( `Copy JSON failed: ${ exportResult . message } ` ) ;
222255 this . refreshPreview ( ) ;
223256 return ;
224257 }
225258
226- const json = JSON . stringify ( toolState , null , 2 ) ;
227- this . inspector . showObject ( toolState ) ;
259+ this . inspector . showObject ( exportResult . toolState ) ;
228260 if ( typeof this . window . navigator ?. clipboard ?. writeText !== "function" ) {
229- this . statusLog . write ( "toolState JSON preview written to Output Summary. Clipboard API is unavailable.") ;
261+ this . statusLog . error ( "Copy JSON failed: Clipboard API is unavailable.") ;
230262 return ;
231263 }
232264
233265 try {
234- await this . window . navigator . clipboard . writeText ( json ) ;
235- this . statusLog . write ( "toolState JSON copied." ) ;
266+ await this . window . navigator . clipboard . writeText ( exportResult . json ) ;
267+ this . statusLog . write ( "JSON copied." ) ;
236268 } catch ( error ) {
237269 this . statusLog . error ( `Copy JSON failed: ${ error . message } ` ) ;
238270 }
239271 }
272+
273+ downloadJson ( exportResult ) {
274+ const documentRef = this . window . document ;
275+ const BlobConstructor = this . window . Blob ;
276+ const urlApi = this . window . URL ;
277+ if ( ! documentRef ?. body || typeof BlobConstructor !== "function" || typeof urlApi ?. createObjectURL !== "function" ) {
278+ throw new Error ( "Browser download APIs are unavailable." ) ;
279+ }
280+
281+ const blob = new BlobConstructor ( [ exportResult . json ] , { type : "application/json" } ) ;
282+ const url = urlApi . createObjectURL ( blob ) ;
283+ const link = documentRef . createElement ( "a" ) ;
284+ link . href = url ;
285+ link . download = exportFileName ( exportResult . toolState . payload . name ) ;
286+ try {
287+ documentRef . body . append ( link ) ;
288+ link . click ( ) ;
289+ } finally {
290+ link . remove ( ) ;
291+ urlApi . revokeObjectURL ( url ) ;
292+ }
293+ }
240294}
0 commit comments