Skip to content

Commit 024c250

Browse files
authored
Easy import asset flow (#1051)
* refactor file dropping for ImportAsset component * refactor file dropping for ImportAsset component * add initial modal for import asset * add slider * some fixes for slider * added import * some fixes and renaming * lint * add support for complex glb's * add errors to import modal * add gltf-validator * small fixes * add proper icons on asset explorer * add renaming assets before import
1 parent c8695cd commit 024c250

File tree

32 files changed

+994
-315
lines changed

32 files changed

+994
-315
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"typescript.tsdk": "node_modules/typescript/lib"
3+
}

packages/@dcl/inspector/package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@dcl/inspector/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"esbuild": "^0.18.17",
3636
"ethereum-cryptography": "^2.1.2",
3737
"fp-future": "^1.0.1",
38+
"gltf-validator": "^2.0.0-dev.3.10",
3839
"hotkeys-js": "^3.13.5",
3940
"jest-environment-jsdom": "^29.5.0",
4041
"jest-puppeteer": "^9.0.0",

packages/@dcl/inspector/src/components/AssetPreview/AssetPreview.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@
77
.AssetPreview canvas {
88
width: 100%;
99
}
10+
11+
.AssetPreview .GltfPreview.hidden {
12+
visibility: hidden;
13+
}

packages/@dcl/inspector/src/components/AssetPreview/AssetPreview.tsx

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import * as React from 'react'
1+
import { useCallback, useMemo, useState } from 'react'
22
import { PreviewCamera, PreviewProjection } from '@dcl/schemas'
3+
import cx from 'classnames'
34
import { WearablePreview } from 'decentraland-ui'
4-
import { IoIosImage } from 'react-icons/io'
5+
import { AiFillSound } from 'react-icons/ai'
6+
import { IoVideocamOutline } from 'react-icons/io5'
7+
import { FaFile } from 'react-icons/fa'
58

6-
import { isAsset as isGltf } from '../EntityInspector/GltfInspector/utils'
79
import { toWearableWithBlobs } from './utils'
810
import { Props } from './types'
911

@@ -14,36 +16,57 @@ const WIDTH = 300
1416
const HEIGHT = 300
1517

1618
export function AssetPreview({ value, resources, onScreenshot, onLoad }: Props) {
17-
return (
18-
<div className="AssetPreview">
19-
{isGltf(value.name) ? (
20-
<GltfPreview value={value} resources={resources} onScreenshot={onScreenshot} onLoad={onLoad} />
21-
) : value.name.endsWith('png') ? (
22-
<PngPreview value={value} onScreenshot={onScreenshot} onLoad={onLoad} />
23-
) : (
24-
<IoIosImage />
25-
)}
26-
</div>
27-
)
19+
const preview = useMemo(() => {
20+
const ext = value.name.split('.').pop()
21+
switch (ext) {
22+
case 'gltf':
23+
case 'glb':
24+
return <GltfPreview value={value} resources={resources} onScreenshot={onScreenshot} onLoad={onLoad} />
25+
case 'png':
26+
case 'jpg':
27+
case 'jpeg':
28+
return <PngPreview value={value} onScreenshot={onScreenshot} onLoad={onLoad} />
29+
case 'mp3':
30+
case 'wav':
31+
case 'ogg':
32+
return <AiFillSound />
33+
case 'mp4':
34+
return <IoVideocamOutline />
35+
default:
36+
return <FaFile />
37+
}
38+
}, [])
39+
40+
return <div className="AssetPreview">{preview}</div>
2841
}
2942

3043
function GltfPreview({ value, resources, onScreenshot, onLoad }: Props) {
31-
const handleLoad = React.useCallback(() => {
44+
const [loading, setLoading] = useState(true)
45+
const handleLoad = useCallback(() => {
3246
onLoad?.()
3347
const wp = WearablePreview.createController(value.name)
34-
void wp.scene.getScreenshot(WIDTH, HEIGHT).then(($) => onScreenshot($))
48+
void wp.scene.getScreenshot(WIDTH, HEIGHT).then(($) => {
49+
setTimeout(() => {
50+
onScreenshot($)
51+
setLoading(false)
52+
}, 1000) // ugly hack to avoid iframe flickering...
53+
})
3554
}, [onLoad])
3655

3756
return (
38-
<WearablePreview
39-
id={value.name}
40-
blob={toWearableWithBlobs(value, resources)}
41-
disableAutoRotate
42-
disableBackground
43-
projection={PreviewProjection.ORTHOGRAPHIC}
44-
camera={PreviewCamera.STATIC}
45-
onLoad={handleLoad}
46-
/>
57+
<>
58+
<div className={cx('GltfPreview', { hidden: loading })}>
59+
<WearablePreview
60+
id={value.name}
61+
blob={toWearableWithBlobs(value, resources)}
62+
disableAutoRotate
63+
disableBackground
64+
projection={PreviewProjection.ORTHOGRAPHIC}
65+
camera={PreviewCamera.STATIC}
66+
onLoad={handleLoad}
67+
/>
68+
</div>
69+
</>
4770
)
4871
}
4972

packages/@dcl/inspector/src/components/Assets/Assets.css

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
padding-left: 8px;
1818
display: flex;
1919
background: var(--tree-bg-color);
20-
height: 36px;
20+
padding: 5px 0;
2121
position: relative;
22+
align-items: center;
23+
justify-content: center;
2224
}
2325

2426
.Assets .Assets-buttons > div > div {
@@ -61,20 +63,22 @@
6163
user-select: none;
6264
}
6365

64-
.Assets .Assets-buttons > div:last-child svg {
65-
width: 16px;
66-
height: 16px;
66+
.Assets .Assets-buttons > button:first-child {
67+
margin: 3px;
68+
position: absolute;
69+
left: 0px;
70+
margin-left: 8px;
71+
display: flex;
72+
align-items: center;
6773
}
6874

69-
.Assets .Assets-buttons > div:last-child {
70-
position: absolute;
71-
right: 0px;
72-
margin-right: 8px;
75+
.Assets .Assets-buttons > button:first-child svg {
76+
margin-right: 5px;
7377
}
7478

7579
.Assets .Assets-content {
7680
background: var(--tree-bg-color) !important;
77-
height: calc(100% - 36px);
81+
height: 100%;
7882
}
7983

8084
.Assets .Assets-content.Hide {

packages/@dcl/inspector/src/components/Assets/Assets.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback } from 'react'
1+
import React, { useCallback, useRef } from 'react'
22
import cx from 'classnames'
33
import { MdImageSearch } from 'react-icons/md'
44
import { HiOutlinePlus } from 'react-icons/hi'
@@ -17,6 +17,8 @@ import { CustomAssets } from '../CustomAssets'
1717
import { selectCustomAssets } from '../../redux/app'
1818
import { RenameAsset } from '../RenameAsset'
1919
import { CreateCustomAsset } from '../CreateCustomAsset'
20+
import { InputRef } from '../FileInput/FileInput'
21+
import { Button } from '../Button'
2022

2123
import './Assets.css'
2224

@@ -31,6 +33,7 @@ function Assets({ isAssetsPanelCollapsed }: { isAssetsPanelCollapsed: boolean })
3133
const dispatch = useAppDispatch()
3234
const tab = useAppSelector(getSelectedAssetsTab)
3335
const customAssets = useAppSelector(selectCustomAssets)
36+
const inputRef = useRef<InputRef>(null)
3437

3538
const handleTabClick = useCallback(
3639
(tab: AssetsTab) => () => {
@@ -47,9 +50,17 @@ function Assets({ isAssetsPanelCollapsed }: { isAssetsPanelCollapsed: boolean })
4750
const assetToRename = useAppSelector(selectAssetToRename)
4851
const stagedCustomAsset = useAppSelector(selectStagedCustomAsset)
4952

53+
const handleImportClick = useCallback(() => {
54+
inputRef.current?.onClick()
55+
}, [inputRef])
56+
5057
return (
5158
<div className="Assets">
5259
<div className="Assets-buttons">
60+
<Button onClick={handleImportClick}>
61+
<HiOutlinePlus />
62+
IMPORT ASSETS
63+
</Button>
5364
<div className="tab" onClick={handleTabClick(AssetsTab.FileSystem)} data-test-id={AssetsTab.FileSystem}>
5465
<div className={cx({ underlined: tab === AssetsTab.FileSystem })}>
5566
<FolderOpen />
@@ -70,22 +81,18 @@ function Assets({ isAssetsPanelCollapsed }: { isAssetsPanelCollapsed: boolean })
7081
<span>ASSET PACKS</span>
7182
</div>
7283
</div>
73-
<div className="tab" onClick={handleTabClick(AssetsTab.Import)} data-test-id={AssetsTab.Import}>
74-
<div>
75-
<HiOutlinePlus />
76-
</div>
77-
</div>
78-
</div>
79-
<div className={cx('Assets-content', { Hide: isAssetsPanelCollapsed })}>
80-
{tab === AssetsTab.AssetsPack && <AssetsCatalog catalog={filteredCatalog} />}
81-
{tab === AssetsTab.FileSystem && <ProjectAssetExplorer />}
82-
{tab === AssetsTab.Import && <ImportAsset onSave={handleTabClick(AssetsTab.FileSystem)} />}
83-
{tab === AssetsTab.CustomAssets && <CustomAssets />}
84-
{tab === AssetsTab.RenameAsset && assetToRename && (
85-
<RenameAsset assetId={assetToRename.id} currentName={assetToRename.name} />
86-
)}
87-
{tab === AssetsTab.CreateCustomAsset && stagedCustomAsset && <CreateCustomAsset />}
8884
</div>
85+
<ImportAsset onSave={handleTabClick(AssetsTab.FileSystem)} ref={inputRef}>
86+
<div className={cx('Assets-content', { Hide: isAssetsPanelCollapsed })}>
87+
{tab === AssetsTab.AssetsPack && <AssetsCatalog catalog={filteredCatalog} />}
88+
{tab === AssetsTab.FileSystem && <ProjectAssetExplorer />}
89+
{tab === AssetsTab.CustomAssets && <CustomAssets />}
90+
{tab === AssetsTab.RenameAsset && assetToRename && (
91+
<RenameAsset assetId={assetToRename.id} currentName={assetToRename.name} />
92+
)}
93+
{tab === AssetsTab.CreateCustomAsset && stagedCustomAsset && <CreateCustomAsset />}
94+
</div>
95+
</ImportAsset>
8996
</div>
9097
)
9198
}

packages/@dcl/inspector/src/components/AssetsCatalog/AssetsCatalog.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
22

33
import { AssetPack } from '../../lib/logic/catalog'
44
import { analytics, Event } from '../../lib/logic/analytics'
5-
import { useAppDispatch } from '../../redux/hooks'
6-
import { selectAssetsTab } from '../../redux/ui'
7-
import { AssetsTab } from '../../redux/ui/types'
85

96
import { Header } from './Header'
107
import { Themes } from './Themes'
@@ -16,16 +13,11 @@ import { Props } from './types'
1613
import './AssetsCatalog.css'
1714

1815
const AssetsCatalog: React.FC<Props> = ({ catalog }) => {
19-
const dispatch = useAppDispatch()
2016
const [selectedTheme, setSelectedTheme] = useState<AssetPack>()
2117
const [search, setSearch] = useState<string>('')
2218

2319
const handleThemeChange = useCallback((value?: AssetPack) => setSelectedTheme(value), [setSelectedTheme])
2420

25-
const handleUploadAsset = useCallback(() => {
26-
dispatch(selectAssetsTab({ tab: AssetsTab.Import }))
27-
}, [])
28-
2921
const handleSearchAssets = useCallback(
3022
(value: string) => {
3123
setSearch(value)
@@ -63,8 +55,8 @@ const AssetsCatalog: React.FC<Props> = ({ catalog }) => {
6355
}, [search, filteredCatalog])
6456

6557
const renderEmptySearch = useCallback(() => {
66-
const ctaMethod = selectedTheme ? handleThemeChange : handleUploadAsset
67-
const ctaText = selectedTheme ? 'search all categories' : 'upload your own asset'
58+
const ctaMethod = selectedTheme ? handleThemeChange : () => undefined
59+
const ctaText = selectedTheme ? 'search all categories' : 'upload your own asset by drag & drop'
6860
return (
6961
<div className="empty-search">
7062
<span>No results for '{search}'.</span>
@@ -77,7 +69,7 @@ const AssetsCatalog: React.FC<Props> = ({ catalog }) => {
7769
</span>
7870
</div>
7971
)
80-
}, [search, selectedTheme, handleThemeChange, handleUploadAsset])
72+
}, [search, selectedTheme, handleThemeChange])
8173

8274
const renderAssets = useCallback(() => {
8375
if (filteredCatalog.length > 0) {

packages/@dcl/inspector/src/components/CreateCustomAsset/CreateCustomAsset.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,15 @@
3232
}
3333

3434
.CreateCustomAsset .preview-container .AssetPreview {
35+
display: none;
36+
}
37+
38+
.CreateCustomAsset .preview-container .thumbnail {
3539
width: 100%;
3640
height: 100%;
41+
background-color: var(--background-gray);
42+
background-size: cover;
43+
background-position: center;
3744
}
3845

3946
.CreateCustomAsset .loader-container {

packages/@dcl/inspector/src/components/CreateCustomAsset/CreateCustomAsset.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,12 @@ const CreateCustomAsset: React.FC = () => {
112112
<div className="file-container">
113113
{previewFile && resources !== null ? (
114114
<div className="preview-container">
115-
{isGeneratingThumbnail && (
115+
{isGeneratingThumbnail && !thumbnail ? (
116116
<div className="loader-container">
117117
<Loader active size="small" />
118118
</div>
119+
) : (
120+
<div className="thumbnail" style={{ backgroundImage: `url(${thumbnail})` }}></div>
119121
)}
120122
<AssetPreview value={previewFile} resources={resources} onScreenshot={handleScreenshot} />
121123
</div>

0 commit comments

Comments
 (0)