Refactoring round one: HttpFontStore/HttpSoundStore/HttpTextureStore collapse into a generic HttpFileStore over a FileStore contract; fontImport/soundImport/textureImport share assetImport with an ImportKind per sort; ImagePicker/FontPicker/SoundPicker sit on FilePicker and describe only their row. The group/comment drag logic leaves GraphCanvas for annotationDrag.ts (with its own unit test), declaredRows.ts replaces the duplicated row build in particles.ts and shaders.ts, and dead code in font/sound/compile/emission/transform/actions is gone. README: asset list, check count 958, the two-job CI, the Scenes wiki page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
48 lines
1.8 KiB
TypeScript
48 lines
1.8 KiB
TypeScript
import { CreateSoundCommand } from '../core/commands/assetCommands';
|
||
import type { SoundStore, UploadedSound } from '../core/store/SoundStore';
|
||
import { type ImportKind, type ImportOutcome, importFiles, importStored, pickAndImport } from './assetImport';
|
||
import type { EditorState } from './state';
|
||
|
||
/** What the file dialog offers and a drop accepts; the server rejects everything else anyway. */
|
||
export const SOUND_ACCEPT = '.mp3,.ogg,.wav,.m4a';
|
||
|
||
const SOUND_EXTENSION = /\.(mp3|ogg|wav|m4a)$/i;
|
||
|
||
/**
|
||
* Sounds are told apart by their name, not their MIME type: browsers report a dropped `.m4a` as
|
||
* `audio/mp4`, `audio/x-m4a` or nothing at all, and an `.ogg` as `audio/ogg` or `video/ogg`.
|
||
*/
|
||
export function isSoundFileName(name: string): boolean {
|
||
return SOUND_EXTENSION.test(name);
|
||
}
|
||
|
||
export interface SoundImportDeps {
|
||
state: EditorState;
|
||
sounds: SoundStore;
|
||
}
|
||
|
||
const SOUND_KIND: ImportKind<UploadedSound, SoundImportDeps> = {
|
||
type: 'sound',
|
||
noun: 'sound',
|
||
fallbackName: 'Sound',
|
||
store: (deps) => deps.sounds,
|
||
// The bytes go up untouched: nothing here could encode audio, and a re-encode would be a loss.
|
||
prepare: (file) =>
|
||
isSoundFileName(file.name)
|
||
? Promise.resolve({ blob: file, type: file.type || 'application/octet-stream' })
|
||
: Promise.reject(new Error('Not a sound file – use MP3, OGG, WAV or M4A.')),
|
||
create: (name, sound) => new CreateSoundCommand(name, sound.file),
|
||
};
|
||
|
||
export function importSounds(files: readonly File[], deps: SoundImportDeps): Promise<ImportOutcome[]> {
|
||
return importFiles(SOUND_KIND, files, deps);
|
||
}
|
||
|
||
export function importStoredSound(sound: UploadedSound, deps: SoundImportDeps): ImportOutcome {
|
||
return importStored(SOUND_KIND, sound, deps);
|
||
}
|
||
|
||
export function pickAndImportSounds(deps: SoundImportDeps): void {
|
||
pickAndImport(SOUND_KIND, SOUND_ACCEPT, deps);
|
||
}
|