Skip to content

Commit

Permalink
fix: Retrieve base wearables from the catalyst (#1706)
Browse files Browse the repository at this point in the history
* fix: Retrieve base wearables from the catalyst

* fix: Change the Preview's variable name

* fix: Remove unused imports

* fix: Internationalize the loading screen

* fix: Remove male and female harcoded values

* chore: Add actions, reducers, selectors and utils tests

* fix: calls

* fix: Use effect on fetch call
  • Loading branch information
LautaroPetaccio authored Dec 23, 2021
1 parent 6561087 commit 0a33d7a
Show file tree
Hide file tree
Showing 30 changed files with 868 additions and 4,828 deletions.
4 changes: 3 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ module.exports = {
testEnvironment: 'jsdom',
moduleNameMapper: {
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
'^!?raw-loader!.*$': 'identity-obj-proxy'
'^!?raw-loader!.*$': 'identity-obj-proxy',
'^three/.*$': 'identity-obj-proxy',
'^@babylonjs.*$': 'identity-obj-proxy'
},
modulePaths: ['<rootDir>/src/'],
setupFiles: ['<rootDir>/src/setupTests.ts']
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { connect } from 'react-redux'
import { RootState } from 'modules/common/types'
import { getBaseWearables } from 'modules/editor/selectors'
import { MapStateProps } from './AvatarWearableDropdown.types'
import AvatarWearableDropdown from './AvatarWearableDropdown'

const mapState = (state: RootState): MapStateProps => {
return {
baseWearables: getBaseWearables(state)
}
}

export default connect(mapState)(AvatarWearableDropdown)
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import React from 'react'
import { Dropdown, DropdownItemProps, DropdownProps } from 'decentraland-ui'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { findWearable, getWearables } from 'modules/editor/avatar'
import { getName } from 'modules/editor/utils'
import { getName, filterWearables } from 'modules/editor/utils'
import { Props } from './AvatarWearableDropdown.types'

export default class AvatarWearableDropdown extends React.PureComponent<Props> {
handleAvatarChangeWearable = (_event: React.SyntheticEvent<HTMLElement, Event>, data: DropdownProps) => {
const { wearable, category, bodyShape, onChange } = this.props
const newWearable = data.value ? findWearable(data.value as string)! : null
const { wearable, category, bodyShape, onChange, baseWearables } = this.props
const newWearable = data.value ? baseWearables.find(wearable => wearable.id === data.value) : null
if (wearable !== newWearable) {
onChange(category, bodyShape, newWearable)
onChange(category, bodyShape, newWearable ?? null)
}
}

render() {
const { wearable, bodyShape, category, label, isNullable } = this.props
const options: DropdownItemProps[] = getWearables(category, bodyShape).map(wearable => ({
const { wearable, bodyShape, category, label, isNullable, baseWearables } = this.props
const options: DropdownItemProps[] = filterWearables(baseWearables, category, bodyShape).map(wearable => ({
value: wearable.id,
text: getName(wearable)
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ export type Props = {
label: string
isNullable?: boolean
onChange: (category: WearableCategory, bodyShape: WearableBodyShape, wearable: Wearable | null) => void
baseWearables: Wearable[]
}

export type MapStateProps = Pick<Props, 'baseWearables'>
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import AvatarWearableDropdown from './AvatarWearableDropdown'
import AvatarWearableDropdown from './AvatarWearableDropdown.container'
export default AvatarWearableDropdown
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from 'modules/editor/actions'
import {
getAvatarAnimation,
getBaseWearables,
getSelectedBaseWearables,
getBodyShape,
getEyeColor,
getHairColor,
Expand All @@ -23,10 +23,10 @@ import CenterPanel from './CenterPanel'

const mapState = (state: RootState): MapStateProps => {
const bodyShape = getBodyShape(state)
const baseWearables = getBaseWearables(state)
const selectedBaseWearables = getSelectedBaseWearables(state)
return {
bodyShape,
baseWearables: baseWearables[bodyShape],
bodyShapeBaseWearables: selectedBaseWearables ? selectedBaseWearables[bodyShape] : null,
skinColor: getSkinColor(state),
eyeColor: getEyeColor(state),
hairColor: getHairColor(state),
Expand Down
70 changes: 39 additions & 31 deletions src/components/ItemEditorPage/CenterPanel/CenterPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export default class CenterPanel extends React.PureComponent<Props, State> {
}

render() {
const { bodyShape, skinColor, eyeColor, hairColor, avatarAnimation, baseWearables } = this.props
const { bodyShape, skinColor, eyeColor, hairColor, avatarAnimation, bodyShapeBaseWearables } = this.props
const { isShowingAvatarAttributes } = this.state

return (
Expand Down Expand Up @@ -146,42 +146,50 @@ export default class CenterPanel extends React.PureComponent<Props, State> {
/>
</div>
<div className="dropdown-container">
<AvatarWearableDropdown
wearable={baseWearables[WearableCategory.HAIR]}
category={WearableCategory.HAIR}
bodyShape={bodyShape}
label={t('wearable.category.hair')}
onChange={this.handleWearableChange}
isNullable
/>
{bodyShapeBaseWearables && (
<AvatarWearableDropdown
wearable={bodyShapeBaseWearables[WearableCategory.HAIR]}
category={WearableCategory.HAIR}
bodyShape={bodyShape}
label={t('wearable.category.hair')}
onChange={this.handleWearableChange}
isNullable
/>
)}
</div>
<div className="dropdown-container">
<AvatarWearableDropdown
wearable={baseWearables[WearableCategory.FACIAL_HAIR]}
category={WearableCategory.FACIAL_HAIR}
bodyShape={bodyShape}
label={t('wearable.category.facial_hair')}
onChange={this.handleWearableChange}
isNullable
/>
{bodyShapeBaseWearables && (
<AvatarWearableDropdown
wearable={bodyShapeBaseWearables[WearableCategory.FACIAL_HAIR]}
category={WearableCategory.FACIAL_HAIR}
bodyShape={bodyShape}
label={t('wearable.category.facial_hair')}
onChange={this.handleWearableChange}
isNullable
/>
)}
</div>
<div className="dropdown-container">
<AvatarWearableDropdown
wearable={baseWearables[WearableCategory.UPPER_BODY]}
category={WearableCategory.UPPER_BODY}
bodyShape={bodyShape}
label={t('wearable.category.upper_body')}
onChange={this.handleWearableChange}
/>
{bodyShapeBaseWearables && (
<AvatarWearableDropdown
wearable={bodyShapeBaseWearables[WearableCategory.UPPER_BODY]}
category={WearableCategory.UPPER_BODY}
bodyShape={bodyShape}
label={t('wearable.category.upper_body')}
onChange={this.handleWearableChange}
/>
)}
</div>
<div className="dropdown-container">
<AvatarWearableDropdown
wearable={baseWearables[WearableCategory.LOWER_BODY]}
category={WearableCategory.LOWER_BODY}
bodyShape={bodyShape}
label={t('wearable.category.lower_body')}
onChange={this.handleWearableChange}
/>
{bodyShapeBaseWearables && (
<AvatarWearableDropdown
wearable={bodyShapeBaseWearables[WearableCategory.LOWER_BODY]}
category={WearableCategory.LOWER_BODY}
bodyShape={bodyShape}
label={t('wearable.category.lower_body')}
onChange={this.handleWearableChange}
/>
)}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type Props = {
eyeColor: Color4
hairColor: Color4
avatarAnimation: AvatarAnimation
baseWearables: Record<WearableCategory, Wearable | null>
bodyShapeBaseWearables: Record<WearableCategory, Wearable | null> | null
visibleItems: Item[]
onSetBodyShape: typeof setBodyShape
onSetAvatarAnimation: typeof setAvatarAnimation
Expand All @@ -42,7 +42,7 @@ export type State = {

export type MapStateProps = Pick<
Props,
'bodyShape' | 'skinColor' | 'eyeColor' | 'hairColor' | 'avatarAnimation' | 'visibleItems' | 'baseWearables'
'bodyShape' | 'skinColor' | 'eyeColor' | 'hairColor' | 'avatarAnimation' | 'visibleItems' | 'bodyShapeBaseWearables'
>
export type MapDispatchProps = Pick<
Props,
Expand Down
22 changes: 11 additions & 11 deletions src/components/Preview/Preview.container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@ import { connect } from 'react-redux'

import { openEditor } from 'modules/editor/actions'
import { RootState } from 'modules/common/types'
import { isReady } from 'modules/editor/selectors'
import { isLoadingBaseWearables, isReady } from 'modules/editor/selectors'
import { getCurrentProject } from 'modules/project/selectors'
import { dropItem } from 'modules/scene/actions'
import { MapStateProps, MapDispatch, MapDispatchProps } from './Preview.types'
import { OpenEditorOptions, PreviewType } from 'modules/editor/types'
import { MapStateProps, MapDispatch, MapDispatchProps, OwnProps } from './Preview.types'
import Preview from './Preview'
import { OpenEditorOptions } from 'modules/editor/types'

const mapState = (state: RootState): MapStateProps => ({
isLoading: !isReady(state),
project: getCurrentProject(state)!
})
const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => {
return {
isLoadingEditor: !isReady(state),
isLoadingBaseWearables: ownProps.type === PreviewType.WEARABLE && isLoadingBaseWearables(state),
project: getCurrentProject(state)!
}
}

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onOpenEditor: (options: Partial<OpenEditorOptions> = {}) => dispatch(openEditor(options)),
onDropItem: (asset, x, y) => dispatch(dropItem(asset, x, y))
})

export default connect(
mapState,
mapDispatch
)(Preview)
export default connect(mapState, mapDispatch)(Preview)
4 changes: 4 additions & 0 deletions src/components/Preview/Preview.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
flex-direction: column;
}

.Preview-wrapper .loading-text {
margin-top: 20px;
}

.Preview {
flex: 1;
height: 100%;
Expand Down
27 changes: 20 additions & 7 deletions src/components/Preview/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { DropTarget } from 'react-dnd'
import Lottie from 'react-lottie'
import { env } from 'decentraland-commons'

import animationData from './loader.json'

import { PreviewType } from 'modules/editor/types'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { ASSET_TYPE } from 'components/AssetCard/AssetCard.dnd'
import { convertToUnityKeyboardEvent } from 'modules/editor/utils'
import { previewTarget, collect, CollectedProps } from './Preview.dnd'
import { EditorWindow, Props, State } from './Preview.types'
import animationData from './loader.json'
import './Preview.css'
import { PreviewType } from 'modules/editor/types'

const editorWindow = window as EditorWindow
const unityDebugParams = env.get('REACT_APP_UNITY_DEBUG_PARAMS')
Expand Down Expand Up @@ -74,7 +74,7 @@ class Preview extends React.Component<Props & CollectedProps, State> {
}
try {
isDCLInitialized = true
; (window as any).devicePixelRatio = 1 // without this unity blows up majestically 💥🌈🦄🔥🤷🏼‍♂️
;(window as any).devicePixelRatio = 1 // without this unity blows up majestically 💥🌈🦄🔥🤷🏼‍♂️
await editorWindow.editor.initEngine(this.canvasContainer.current, '/unity/Build/unity.json')
if (!unityDebugParams) {
canvas = await editorWindow.editor.getDCLCanvas()
Expand All @@ -91,12 +91,24 @@ class Preview extends React.Component<Props & CollectedProps, State> {
}
}

getLoadingText(): string {
const { isLoadingEditor, isLoadingBaseWearables } = this.props
if (isLoadingBaseWearables && isLoadingEditor) {
return t('editor_preview.loading_unity_and_base_wearables')
} else if (isLoadingBaseWearables && !isLoadingEditor) {
return t('editor_preview.loading_base_wearables')
} else {
return t('editor_preview.loading_unity')
}
}

render() {
const { isLoading, connectDropTarget } = this.props
const { isLoadingEditor, connectDropTarget, isLoadingBaseWearables } = this.props
const isLoadingResources = isLoadingEditor || isLoadingBaseWearables

return connectDropTarget(
<div className="Preview-wrapper">
{isLoading && (
{isLoadingResources && (
<div className="overlay">
<Lottie
height={100}
Expand All @@ -113,9 +125,10 @@ class Preview extends React.Component<Props & CollectedProps, State> {
<div id="progress-bar" className="progress ingame">
<div className="full"></div>
</div>
<div className="loading-text">{this.getLoadingText()}</div>
</div>
)}
<div className={`Preview ${isLoading ? 'loading' : ''}`} id="preview-viewport" ref={this.canvasContainer} />
<div className={`Preview ${isLoadingResources ? 'loading' : ''}`} id="preview-viewport" ref={this.canvasContainer} />
</div>
)
}
Expand Down
11 changes: 7 additions & 4 deletions src/components/Preview/Preview.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
TogglePreviewAction,
CloseEditorAction,
SetScriptUrlAction,
UpdateAvatarAction
UpdateAvatarAction,
FetchBaseWearablesRequestAction
} from 'modules/editor/actions'
import { UnityKeyboardEvent, OpenEditorOptions } from 'modules/editor/types'
import { dropItem, DropItemAction } from 'modules/scene/actions'
Expand Down Expand Up @@ -60,14 +61,16 @@ export type EditorWindow = typeof window & {
}

export type Props = Partial<OpenEditorOptions> & {
isLoading: boolean
isLoadingEditor: boolean
onOpenEditor: typeof openEditor
onDropItem: typeof dropItem
project: Project
isLoadingBaseWearables: boolean
}

export type State = {}

export type MapStateProps = Pick<Props, 'isLoading' | 'project'>
export type MapStateProps = Pick<Props, 'isLoadingEditor' | 'project' | 'isLoadingBaseWearables'>
export type MapDispatchProps = Pick<Props, 'onOpenEditor' | 'onDropItem'>
export type MapDispatch = Dispatch<SetEditorReadyAction | OpenEditorAction | DropItemAction>
export type MapDispatch = Dispatch<SetEditorReadyAction | OpenEditorAction | DropItemAction | FetchBaseWearablesRequestAction>
export type OwnProps = Pick<Props, 'type'>
35 changes: 35 additions & 0 deletions src/modules/editor/actions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { wearable } from 'specs/editor'
import {
fetchBaseWearablesFailure,
fetchBaseWearablesRequest,
fetchBaseWearablesSuccess,
FETCH_BASE_WEARABLES_FAILURE,
FETCH_BASE_WEARABLES_REQUEST,
FETCH_BASE_WEARABLES_SUCCESS
} from './actions'

const commonError = 'anError'

describe('when creating the action that signals the start of a base wearables fetch request', () => {
it('should return an action signaling the start of the base wearables fetch', () => {
expect(fetchBaseWearablesRequest()).toEqual({ type: FETCH_BASE_WEARABLES_REQUEST })
})
})

describe('when creating the action that signals the successful fetch of tiers', () => {
it('should return an action signaling the success of the base wearables fetch', () => {
expect(fetchBaseWearablesSuccess([wearable])).toEqual({
type: FETCH_BASE_WEARABLES_SUCCESS,
payload: { wearables: [wearable] }
})
})
})

describe('when creating the action that signals the unsuccessful fetch of tiers', () => {
it('should return an action signaling the failure of the base wearables fetch', () => {
expect(fetchBaseWearablesFailure(commonError)).toEqual({
type: FETCH_BASE_WEARABLES_FAILURE,
payload: { error: commonError }
})
})
})
14 changes: 14 additions & 0 deletions src/modules/editor/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,17 @@ export const updateAvatar = (wearables: Wearable[], skinColor: Color4, eyeColor:
action(UPDATE_AVATAR, { wearables, animation, skinColor, eyeColor, hairColor })

export type UpdateAvatarAction = ReturnType<typeof updateAvatar>

// Fetch Base Wearables

export const FETCH_BASE_WEARABLES_REQUEST = '[Request] Fetch base wearables'
export const FETCH_BASE_WEARABLES_SUCCESS = '[Success] Fetch base wearables'
export const FETCH_BASE_WEARABLES_FAILURE = '[Failure] Fetch base wearables'

export const fetchBaseWearablesRequest = () => action(FETCH_BASE_WEARABLES_REQUEST)
export const fetchBaseWearablesSuccess = (wearables: Wearable[]) => action(FETCH_BASE_WEARABLES_SUCCESS, { wearables })
export const fetchBaseWearablesFailure = (error: string) => action(FETCH_BASE_WEARABLES_FAILURE, { error })

export type FetchBaseWearablesRequestAction = ReturnType<typeof fetchBaseWearablesRequest>
export type FetchBaseWearablesSuccessAction = ReturnType<typeof fetchBaseWearablesSuccess>
export type FetchBaseWearablesFailureAction = ReturnType<typeof fetchBaseWearablesFailure>
Loading

1 comment on commit 0a33d7a

@vercel
Copy link

@vercel vercel bot commented on 0a33d7a Dec 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.