diff --git a/src/app/App.tsx b/src/app/App.tsx index 8ca5d2a8..b50b0c8f 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -760,6 +760,7 @@ export function App({ store, initialStatusMessage }: AppProps) { const importInputRef = useRef(null); const importModelInputRef = useRef(null); const importBackgroundImageInputRef = useRef(null); + const importAudioInputRef = useRef(null); const loadedModelAssetsRef = useRef>({}); const loadedImageAssetsRef = useRef>({}); const loadedAudioAssetsRef = useRef>({}); @@ -2732,6 +2733,10 @@ export function App({ store, initialStatusMessage }: AppProps) { importBackgroundImageInputRef.current?.click(); }; + const handleImportAudioButtonClick = () => { + importAudioInputRef.current?.click(); + }; + const handleImportModelChange = async (event: ChangeEvent) => { const input = event.currentTarget; const files = Array.from(input.files ?? []); @@ -2841,6 +2846,56 @@ export function App({ store, initialStatusMessage }: AppProps) { } }; + const handleImportAudioChange = async (event: ChangeEvent) => { + const input = event.currentTarget; + const file = input.files?.[0]; + + if (file === undefined) { + return; + } + + if (projectAssetStorage === null) { + setAssetStatusMessage("Imported audio assets require project asset storage. IndexedDB is unavailable in this browser."); + input.value = ""; + return; + } + + let importedAudioForCleanup: { asset: AudioAssetRecord; loadedAsset: LoadedAudioAsset } | null = null; + + try { + const importedAudio = await importAudioAssetFromFile(file, projectAssetStorage); + importedAudioForCleanup = importedAudio; + + store.executeCommand( + createImportAudioAssetCommand({ + asset: importedAudio.asset, + label: `Import ${importedAudio.asset.sourceName}` + }) + ); + + loadedAudioAssetsRef.current = { + ...loadedAudioAssetsRef.current, + [importedAudio.asset.id]: importedAudio.loadedAsset + }; + setLoadedAudioAssets((currentLoadedAssets) => ({ + ...currentLoadedAssets, + [importedAudio.asset.id]: importedAudio.loadedAsset + })); + setAssetStatusMessage(null); + setStatusMessage(`Imported ${importedAudio.asset.sourceName} and registered it as an audio asset.`); + } catch (error) { + if (importedAudioForCleanup !== null) { + await projectAssetStorage.deleteAsset(importedAudioForCleanup.asset.storageKey).catch(() => undefined); + } + + const message = getErrorMessage(error); + setStatusMessage(message); + setAssetStatusMessage(message); + } finally { + input.value = ""; + } + }; + const applyFaceMaterial = (materialId: string) => { if (selectedBrush === null || selectedFaceId === null || selectedFace === null) { setStatusMessage("Select a single box face before applying a material.");