Skip to content

Commit

Permalink
improve: asset import (#760)
Browse files Browse the repository at this point in the history
* improve: asset import

* add: support for .mp3/.wav/.ogg files
  • Loading branch information
nicoecheza authored Oct 4, 2023
1 parent beb3642 commit d7b961d
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PropTypes } from './types'
function parseAccept(accept: PropTypes['accept']) {
let value = ''
for (const [key, values] of Object.entries(accept ?? {})) {
value += `${key},${values.join(',')}`
value += `${key},${values.join(',')},`
}
return value
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
overflow: hidden;
white-space: nowrap;
width: inherit;
text-align: center;
}

.ImportAsset .file-container {
Expand Down
68 changes: 55 additions & 13 deletions packages/@dcl/inspector/src/components/ImportAsset/ImportAsset.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useState } from 'react'
import { HiOutlineUpload } from 'react-icons/hi'
import { RxCross2 } from 'react-icons/rx'
import { RxCross2, RxReload } from 'react-icons/rx'
import { IoIosImage } from 'react-icons/io'

import FileInput from '../FileInput'
Expand All @@ -18,9 +18,17 @@ import { DIRECTORY, withAssetDir } from '../../lib/data-layer/host/fs-utils'
import { importAsset } from '../../redux/data-layer'
import { useAppDispatch, useAppSelector } from '../../redux/hooks'
import { selectAssetCatalog } from '../../redux/app'
import { getRandomMnemonic } from './utils'

const ONE_MB_IN_BYTES = 1_048_576
const ONE_GB_IN_BYTES = ONE_MB_IN_BYTES * 1024
const ACCEPTED_FILE_TYPES = {
'model/gltf-binary': ['.gltf', '.glb'],
'image/png': ['.png'],
'audio/mpeg': ['.mp3'],
'audio/wav': ['.wav'],
'audio/ogg': ['.ogg']
}

interface PropTypes {
onSave(): void
Expand Down Expand Up @@ -61,6 +69,23 @@ async function validateGltf(data: ArrayBuffer): Promise<ValidationError> {
}
}

async function validateAsset(extension: string, data: ArrayBuffer): Promise<ValidationError> {
switch (extension) {
case 'glb':
case 'gltf':
return validateGltf(data)
// add validators for .png/.ktx2?
case 'png':
case 'ktx2':
case 'mp3':
case '.wav':
case '.ogg':
return null
default:
return `Invalid asset format ".${extension}"`
}
}

const ImportAsset: React.FC<PropTypes> = ({ onSave }) => {
// TODO: multiple files
const dispatch = useAppDispatch()
Expand Down Expand Up @@ -96,9 +121,9 @@ const ImportAsset: React.FC<PropTypes> = ({ onSave }) => {
return
}

const gltfValidationError = await validateGltf(binary)
if (gltfValidationError !== null) {
setValidationError(gltfValidationError)
const validationError = await validateAsset(assetExtension, binary)
if (validationError !== null) {
setValidationError(validationError)
return
}

Expand Down Expand Up @@ -127,22 +152,34 @@ const ImportAsset: React.FC<PropTypes> = ({ onSave }) => {
setAssetName(event.target.value)
}, [])

const invalidName = !!assets.find((asset) => {
const [packageName, otherAssetName] = removeBasePath(basePath, asset.path).split('/')
if (packageName === 'builder') return false
else return otherAssetName?.toLocaleLowerCase() === assetName?.toLocaleLowerCase() + '.' + assetExtension
})
const isValidName = useCallback((name: string, ext: string) => {
return !assets.find((asset) => {
const [packageName, otherAssetName] = removeBasePath(basePath, asset.path).split('/')
if (packageName === 'builder') return false
return otherAssetName?.toLocaleLowerCase() === name?.toLocaleLowerCase() + '.' + ext
})
}, [])

const invalidName = !isValidName(assetName, assetExtension)

const generateAssetName = useCallback(() => {
let name: string = assetName
while (!isValidName(name, assetExtension)) {
name = getRandomMnemonic()
}
setAssetName(name)
}, [assetName])

return (
<div className="ImportAsset">
<FileInput disabled={!!file} onDrop={handleDrop} accept={{ 'model/gltf-binary': ['.gltf', '.glb'] }}>
<FileInput disabled={!!file} onDrop={handleDrop} accept={ACCEPTED_FILE_TYPES}>
{!file && (
<>
<div className="upload-icon">
<HiOutlineUpload />
</div>
<span>
To import an asset drag and drop a single GLB or GLTF file
To import an asset drag and drop a single GLB/GLTF/PNG/MP3 file
<br /> or click to select a file.
</span>
</>
Expand All @@ -156,9 +193,14 @@ const ImportAsset: React.FC<PropTypes> = ({ onSave }) => {
<IoIosImage />
<div className="file-title">{file.name}</div>
</Container>
<div className={classNames({ error: !!invalidName })}>
<div className={classNames({ error: invalidName })}>
<Block label="Asset name">
<TextField label="" value={assetName} onChange={handleNameChange} />
<TextField value={assetName} onChange={handleNameChange} />
{invalidName && (
<div onClick={generateAssetName}>
<RxReload />
</div>
)}
</Block>
<Button disabled={invalidName || !!validationError} onClick={handleSave}>
Import
Expand Down
20 changes: 20 additions & 0 deletions packages/@dcl/inspector/src/components/ImportAsset/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { getRandomMnemonic, nouns, adjectives } from './utils'

describe('getRandomMnemonic', () => {
it('should return a string with a random adjective and noun', () => {
const mnemonic = getRandomMnemonic()
const [adjective, noun] = mnemonic.split('-')

expect(adjectives).toContain(adjective)
expect(nouns).toContain(noun)
})

it('should return a valid mnemonic', () => {
const mnemonic = getRandomMnemonic()
const parts = mnemonic.split('-')

expect(parts.length).toBe(2)
expect(adjectives).toContain(parts[0])
expect(nouns).toContain(parts[1])
})
})
91 changes: 91 additions & 0 deletions packages/@dcl/inspector/src/components/ImportAsset/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const sampleIndex = (list: any[]) => Math.floor(Math.random() * list.length)

export function getRandomMnemonic() {
return `${adjectives[sampleIndex(adjectives)]}-${nouns[sampleIndex(nouns)]}`
}

export const nouns = [
'transportation',
'chest',
'variation',
'director',
'tale',
'extent',
'interaction',
'exam',
'combination',
'movie',
'literature',
'significance',
'member',
'priority',
'analyst',
'audience',
'food',
'complaint',
'wedding',
'awareness',
'outcome',
'army',
'resolution',
'ratio',
'baseball',
'family',
'excitement',
'cheek',
'payment',
'freedom',
'sir',
'storage',
'elevator',
'satisfaction',
'organization',
'agency',
'industry',
'arrival',
'thanks',
'hall'
]

export const adjectives = [
'cluttered',
'tricky',
'scandalous',
'rampant',
'well-off',
'abashed',
'fallacious',
'adventurous',
'dizzy',
'huge',
'youthful',
'succinct',
'legal',
'purring',
'wise',
'jagged',
'smart',
'learned',
'relieved',
'plain',
'pushy',
'vast',
'heady',
'vagabond',
'disillusioned',
'obese',
'electric',
'far',
'unaccountable',
'loud',
'early',
'wealthy',
'long',
'thinkable',
'sordid',
'striped',
'violet',
'likeable',
'cheap',
'absorbed'
]
6 changes: 3 additions & 3 deletions packages/@dcl/inspector/src/lib/babylon/setup/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { initKeyboard } from './input'
import { setupEngine } from './setup'
import { InspectorPreferences } from '../../logic/preferences/types'

/*
I refactored the piece that uses canvas and window into this file and ignored it from coverage
because it's not possible to test it without jsdom, and we can't use jsdom because of the ecs
/*
I refactored the piece that uses canvas and window into this file and ignored it from coverage
because it's not possible to test it without jsdom, and we can't use jsdom because of the ecs
*/

export function initRenderer(canvas: HTMLCanvasElement, preferences: InspectorPreferences) {
Expand Down

0 comments on commit d7b961d

Please sign in to comment.