Skip to content

Commit 9ae2f83

Browse files
Feat: Update component dropdown when adding/removing a component (#1159)
* feat: keep components on state an suscribe to PUT/REMOVE component events * feat: show all components and disable the ones already added * feat: remove visibility component when there's no mesh or GLTF * fix: remove debugger * feat: Remove comments * feat: Remove unused * feat: Remove unused parse name * feat: add tests * feat: change logic to get tooltio
1 parent 1438afe commit 9ae2f83

6 files changed

Lines changed: 428 additions & 92 deletions

File tree

packages/@dcl/inspector/src/components/EntityInspector/EntityHeader/EntityHeader.tsx

Lines changed: 105 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -104,21 +104,25 @@ export default React.memo(
104104
[entity]
105105
)
106106

107+
const availableComponents = getAvailableComponents(entity)
108+
107109
const isComponentDisabled = useCallback(
108-
(component: string) => {
109-
switch (component) {
110-
case 'Visibility': {
111-
return !hasGltfContainer && !hasMeshCollider
112-
}
113-
default:
114-
return false
110+
(componentId: number) => {
111+
const componentInfo = availableComponents.find((comp) => comp.id === componentId)
112+
113+
if (componentInfo && componentInfo.isOnEntity) {
114+
return true
115115
}
116+
117+
if (componentId === sdk.components.VisibilityComponent.componentId) {
118+
return !hasGltfContainer && !hasMeshCollider
119+
}
120+
121+
return false
116122
},
117-
[entity, hasGltfContainer, hasMeshCollider]
123+
[availableComponents, hasGltfContainer, hasMeshCollider, sdk.components.VisibilityComponent.componentId]
118124
)
119125

120-
const availableComponents = getAvailableComponents(entity)
121-
122126
const handleOpenModal = useCallback(
123127
(cb?: () => void) => {
124128
setModal({ isOpen: true, cb })
@@ -141,6 +145,27 @@ export default React.memo(
141145
[isBasicViewEnabled, handleAddComponent, handleOpenModal]
142146
)
143147

148+
const getComponentTooltip = useCallback(
149+
(componentId: number, description: string, link?: string) => {
150+
const componentInfo = availableComponents.find((c) => c.id === componentId)
151+
152+
if (componentInfo?.isOnEntity) {
153+
return {
154+
text: 'This component is already added. An entity can only have one copy of each component.'
155+
}
156+
}
157+
158+
if (componentId === sdk.components.VisibilityComponent.componentId) {
159+
return {
160+
text: 'You must have either a GLTF Container or a Mesh Collider component to use this component.'
161+
}
162+
}
163+
164+
return { text: description, ...(link && { link }) }
165+
},
166+
[isComponentDisabled, availableComponents, sdk.components.VisibilityComponent.componentId]
167+
)
168+
144169
const componentOptions = useMemo(() => {
145170
const options = [
146171
{ header: '3D Content' },
@@ -152,19 +177,23 @@ export default React.memo(
152177
sdk.components.GltfContainer.componentId,
153178
sdk.components.GltfContainer.componentName
154179
),
155-
tooltip: {
156-
text: "The GLTF assigns a 3D model file for the item's visible shape. It also handles collisions, to make an item clickable or block the player from walking through it."
157-
}
180+
disabled: isComponentDisabled(sdk.components.GltfContainer.componentId),
181+
tooltip: getComponentTooltip(
182+
sdk.components.GltfContainer.componentId,
183+
"The GLTF assigns a 3D model file for the item's visible shape. It also handles collisions, to make an item clickable or block the player from walking through it."
184+
)
158185
},
159186
{
160187
id: sdk.components.Material.componentId,
161188
value: 'Material',
162189
onClick: () =>
163190
handleClickAddComponent(sdk.components.Material.componentId, sdk.components.Material.componentName),
164-
tooltip: {
165-
text: 'Material determines the visual appearance of an object. It defines properties such as color, texture, and transparency',
166-
link: 'https://docs.decentraland.org/creator/development-guide/sdk7/materials/'
167-
}
191+
disabled: isComponentDisabled(sdk.components.Material.componentId),
192+
tooltip: getComponentTooltip(
193+
sdk.components.Material.componentId,
194+
'Material determines the visual appearance of an object. It defines properties such as color, texture, and transparency',
195+
'https://docs.decentraland.org/creator/development-guide/sdk7/materials/'
196+
)
168197
},
169198
{
170199
id: sdk.components.VisibilityComponent.componentId,
@@ -174,92 +203,96 @@ export default React.memo(
174203
sdk.components.VisibilityComponent.componentId,
175204
sdk.components.VisibilityComponent.componentName
176205
),
177-
tooltip: {
178-
className: 'EntityHeader',
179-
text: (
180-
<span className="VisibilityComponentTooltip">
181-
Visibility controls whether an object is visible or not to the player. Items marked as invisible are
182-
shown on the editor, but not to players running the scene.
183-
{isComponentDisabled('Visibility') && (
184-
<span className="ErrorMessage">
185-
You must have either a GLTF Container or a Mesh Collider component to use this component.
186-
</span>
187-
)}
188-
</span>
189-
)
190-
},
191-
disabled: isComponentDisabled('Visibility')
206+
disabled: isComponentDisabled(sdk.components.VisibilityComponent.componentId),
207+
tooltip: getComponentTooltip(
208+
sdk.components.VisibilityComponent.componentId,
209+
'Visibility controls whether an object is visible or not to the player. Items marked as invisible are shown on the editor, but not to players running the scene.'
210+
)
192211
},
193212
{
194213
id: sdk.components.MeshRenderer.componentId,
195214
value: 'Mesh Renderer',
196215
onClick: () =>
197216
handleClickAddComponent(sdk.components.MeshRenderer.componentId, sdk.components.MeshRenderer.componentName),
198-
tooltip: {
199-
text: 'Use MeshRenderer to assign a primitive 3D shape to the item. Instead of using a 3D file from GLTF, assign a simple cube, plane, sphere, or cylinder. These shapes can be used together with Materials',
200-
link: 'https://docs.decentraland.org/creator/development-guide/sdk7/shape-components/'
201-
}
217+
disabled: isComponentDisabled(sdk.components.MeshRenderer.componentId),
218+
tooltip: getComponentTooltip(
219+
sdk.components.MeshRenderer.componentId,
220+
'Use MeshRenderer to assign a primitive 3D shape to the item. Instead of using a 3D file from GLTF, assign a simple cube, plane, sphere, or cylinder. These shapes can be used together with Materials',
221+
'https://docs.decentraland.org/creator/development-guide/sdk7/shape-components/'
222+
)
202223
},
203224
{
204225
id: sdk.components.MeshCollider.componentId,
205226
value: 'Mesh Collider',
206227
onClick: () =>
207228
handleClickAddComponent(sdk.components.MeshCollider.componentId, sdk.components.MeshCollider.componentName),
208-
tooltip: {
209-
text: 'MeshCollider defines the collision properties of an item, based on its invisible collision geometry. Collisions serve to make an item clickable or to block the player from walking through an item',
210-
link: 'https://docs.decentraland.org/creator/development-guide/sdk7/colliders/'
211-
}
229+
disabled: isComponentDisabled(sdk.components.MeshCollider.componentId),
230+
tooltip: getComponentTooltip(
231+
sdk.components.MeshCollider.componentId,
232+
'MeshCollider defines the collision properties of an item, based on its invisible collision geometry. Collisions serve to make an item clickable or to block the player from walking through an item',
233+
'https://docs.decentraland.org/creator/development-guide/sdk7/colliders/'
234+
)
212235
},
213236
{ header: 'Interaction' },
214237
{
215238
id: sdk.components.States.componentId,
216239
value: 'States',
217240
onClick: () =>
218241
handleClickAddComponent(sdk.components.States.componentId, sdk.components.States.componentName),
219-
tooltip: {
220-
text: 'States specify the status of entities. Use triggers to check or change states, and set actions accordingly.',
221-
link: 'https://docs.decentraland.org/creator/smart-items/#states'
222-
}
242+
disabled: isComponentDisabled(sdk.components.States.componentId),
243+
tooltip: getComponentTooltip(
244+
sdk.components.States.componentId,
245+
'States specify the status of entities. Use triggers to check or change states, and set actions accordingly.',
246+
'https://docs.decentraland.org/creator/smart-items/#states'
247+
)
223248
},
224249
{
225250
id: sdk.components.Triggers.componentId,
226251
value: 'Triggers',
227252
onClick: () =>
228253
handleClickAddComponent(sdk.components.Triggers.componentId, sdk.components.Triggers.componentName),
229-
tooltip: {
230-
text: 'Triggers activate actions based on player interactions like clicks, entering/exiting areas, or global events like "on spawn".',
231-
link: 'https://docs.decentraland.org/creator/smart-items/#triggers'
232-
}
254+
disabled: isComponentDisabled(sdk.components.Triggers.componentId),
255+
tooltip: getComponentTooltip(
256+
sdk.components.Triggers.componentId,
257+
'Triggers activate actions based on player interactions like clicks, entering/exiting areas, or global events like "on spawn".',
258+
'https://docs.decentraland.org/creator/smart-items/#triggers'
259+
)
233260
},
234261
{
235262
id: sdk.components.Actions.componentId,
236263
value: 'Actions',
237264
onClick: () =>
238265
handleClickAddComponent(sdk.components.Actions.componentId, sdk.components.Actions.componentName),
239-
tooltip: {
240-
text: 'Actions list the capabilities of entities, from playing animations to changing visibility. Customize or add new actions, which are activated by triggers.',
241-
link: 'https://docs.decentraland.org/creator/smart-items/#actions'
242-
}
266+
disabled: isComponentDisabled(sdk.components.Actions.componentId),
267+
tooltip: getComponentTooltip(
268+
sdk.components.Actions.componentId,
269+
'Actions list the capabilities of entities, from playing animations to changing visibility. Customize or add new actions, which are activated by triggers.',
270+
'https://docs.decentraland.org/creator/smart-items/#actions'
271+
)
243272
},
244273
{
245274
id: sdk.components.AudioSource.componentId,
246275
value: 'Audio Source',
247276
onClick: () =>
248277
handleClickAddComponent(sdk.components.AudioSource.componentId, sdk.components.AudioSource.componentName),
249-
tooltip: {
250-
text: 'AudioSource enables the playback of sound in your scene. The item emits sound that originates from its location, from an .mp3 file in your scene project',
251-
link: 'https://docs.decentraland.org/creator/development-guide/sdk7/sounds'
252-
}
278+
disabled: isComponentDisabled(sdk.components.AudioSource.componentId),
279+
tooltip: getComponentTooltip(
280+
sdk.components.AudioSource.componentId,
281+
'AudioSource enables the playback of sound in your scene. The item emits sound that originates from its location, from an .mp3 file in your scene project',
282+
'https://docs.decentraland.org/creator/development-guide/sdk7/sounds'
283+
)
253284
},
254285
{
255286
id: sdk.components.TextShape.componentId,
256287
value: 'Text Shape',
257288
onClick: () =>
258289
handleClickAddComponent(sdk.components.TextShape.componentId, sdk.components.TextShape.componentName),
259-
tooltip: {
260-
text: 'Use TextShape to display text in the 3D space',
261-
link: 'https://docs.decentraland.org/creator/development-guide/sdk7/text'
262-
}
290+
disabled: isComponentDisabled(sdk.components.TextShape.componentId),
291+
tooltip: getComponentTooltip(
292+
sdk.components.TextShape.componentId,
293+
'Use TextShape to display text in the 3D space',
294+
'https://docs.decentraland.org/creator/development-guide/sdk7/text'
295+
)
263296
},
264297
{
265298
id: sdk.components.PointerEvents.componentId,
@@ -269,10 +302,12 @@ export default React.memo(
269302
sdk.components.PointerEvents.componentId,
270303
sdk.components.PointerEvents.componentName
271304
),
272-
tooltip: {
273-
text: 'Use PointerEvents to configure the hints shown to players when they hover the cursor over the item. Change the text, the button, the max distance, etc',
274-
link: 'https://docs.decentraland.org/creator/development-guide/sdk7/click-events'
275-
}
305+
disabled: isComponentDisabled(sdk.components.PointerEvents.componentId),
306+
tooltip: getComponentTooltip(
307+
sdk.components.PointerEvents.componentId,
308+
'Use PointerEvents to configure the hints shown to players when they hover the cursor over the item. Change the text, the button, the max distance, etc',
309+
'https://docs.decentraland.org/creator/development-guide/sdk7/click-events'
310+
)
276311
}
277312
]
278313

@@ -283,23 +318,23 @@ export default React.memo(
283318
return set
284319
}, new Set<number>())
285320

286-
const availableIds = availableComponents.reduce((set, component) => set.add(component.id), new Set<number>())
287-
288321
if (availableComponents.some((component) => !optionIds.has(component.id))) {
289322
options.push({ header: 'Other' })
290323
for (const component of availableComponents) {
291324
if (!optionIds.has(component.id)) {
292325
options.push({
293326
id: component.id,
294327
value: component.name,
295-
onClick: () => handleClickAddComponent(component.id, component.name)
296-
} as any)
328+
onClick: () => handleClickAddComponent(component.id, component.name),
329+
disabled: isComponentDisabled(component.id),
330+
tooltip: getComponentTooltip(component.id, `${component.name} component`)
331+
})
297332
}
298333
}
299334
}
300335

301-
return options.filter((option) => !option.id || availableIds.has(option.id))
302-
}, [sdk, availableComponents, isComponentDisabled, handleClickAddComponent])
336+
return options
337+
}, [sdk, availableComponents, isComponentDisabled, handleClickAddComponent, getComponentTooltip])
303338

304339
const quitEditMode = useCallback(() => setEditMode(false), [])
305340
const enterEditMode = useCallback(() => setEditMode(true), [])

packages/@dcl/inspector/src/components/EntityInspector/GltfInspector/GltfInspector.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,18 @@ export default withSdk<Props>(({ sdk, entity }) => {
3030
)
3131

3232
const handleRemove = useCallback(async () => {
33+
const { VisibilityComponent, MeshCollider } = sdk.components
34+
const hasMeshCollider = MeshCollider.has(entity)
35+
const hasVisibility = VisibilityComponent.has(entity)
36+
3337
sdk.operations.removeComponent(entity, GltfContainer)
38+
39+
if (hasVisibility && !hasMeshCollider) {
40+
sdk.operations.removeComponent(entity, VisibilityComponent)
41+
}
42+
3443
await sdk.operations.dispatch()
35-
}, [])
44+
}, [sdk, entity])
3645

3746
const handleDrop = useCallback(async (src: string) => {
3847
const { operations } = sdk

packages/@dcl/inspector/src/components/EntityInspector/MeshColliderInspector/MeshColliderInspector.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,18 @@ export default withSdk<Props>(({ sdk, entity }) => {
1919
const { getInputProps } = useComponentInput(entity, MeshCollider, fromMeshCollider, toMeshCollider, isValidInput)
2020

2121
const handleRemove = useCallback(async () => {
22+
const { VisibilityComponent, GltfContainer } = sdk.components
23+
const hasGltfContainer = GltfContainer.has(entity)
24+
const hasVisibility = VisibilityComponent.has(entity)
25+
2226
sdk.operations.removeComponent(entity, MeshCollider)
27+
28+
if (hasVisibility && !hasGltfContainer) {
29+
sdk.operations.removeComponent(entity, VisibilityComponent)
30+
}
31+
2332
await sdk.operations.dispatch()
24-
}, [])
33+
}, [sdk, entity])
2534

2635
if (!hasMeshCollider) return null
2736

packages/@dcl/inspector/src/components/EntityInspector/VisibilityComponentInspector/VisibilityComponentInspector.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ import { Props } from './types'
1818
export default withSdk<Props>(({ sdk, entity }) => {
1919
const { VisibilityComponent, GltfContainer, MeshCollider } = sdk.components
2020
const hasVisibilityComponent = useHasComponent(entity, VisibilityComponent)
21-
const hasGltfContainer = useHasComponent(entity, GltfContainer)
22-
const hasMeshCollider = useHasComponent(entity, MeshCollider)
2321
const [componentValue, setComponentValue] = useComponentValue<PBVisibilityComponent>(entity, VisibilityComponent)
2422
const [gltfComponentValue, setGltfComponentValue, isGltfComponentEqual] = useComponentValue<PBGltfContainer>(
2523
entity,
@@ -100,7 +98,7 @@ export default withSdk<Props>(({ sdk, entity }) => {
10098
)
10199
}, [])
102100

103-
if (!hasVisibilityComponent || (!hasGltfContainer && !hasMeshCollider)) return null
101+
if (!hasVisibilityComponent) return null
104102

105103
return (
106104
<Container label="Visibility" className={cx('VisibilityContainer')} onRemoveContainer={handleRemove}>

0 commit comments

Comments
 (0)