import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { Object3D } from 'three'
import { useForceUpdate } from '@xrengine/common/src/utils/useForceUpdate'
import { Engine } from '@xrengine/engine/src/ecs/classes/Engine'
import { Entity } from '@xrengine/engine/src/ecs/classes/Entity'
import { getAllComponents } from '@xrengine/engine/src/ecs/functions/ComponentFunctions'
import { EntityOrObjectUUID } from '@xrengine/engine/src/ecs/functions/EntityTree'
import { MaterialComponentType } from '@xrengine/engine/src/renderer/materials/components/MaterialComponent'
import { getMaterialLibrary } from '@xrengine/engine/src/renderer/materials/MaterialLibrary'
import { UUIDComponent } from '@xrengine/engine/src/scene/components/UUIDComponent'
import { EntityNodeEditor } from '../../functions/PrefabEditors'
import { useEditorState } from '../../services/EditorServices'
import { useSelectionState } from '../../services/SelectionServices'
import MaterialEditor from '../materials/MaterialEditor'
import { CoreNodeEditor } from './CoreNodeEditor'
import Object3DNodeEditor from './Object3DNodeEditor'
const StyledNodeEditor = styled.div``
/**
* PropertiesPanelContent used as container element contains content of editor view.
* @type {Styled Component}
*/
const PropertiesPanelContent = styled.div`
overflow-y: auto;
height: 100%;
`
/**
* NoNodeSelectedMessage used to show the message when no selected no is there.
*
* @type {Styled component}
*/
const NoNodeSelectedMessage = styled.div`
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: var(--textColor);
`
/**
* PropertiesPanelContainer used to render editor view to customize property of selected element.
*
* @extends Component
*/
export const PropertiesPanelContainer = () => {
const selectionState = useSelectionState()
const editorState = useEditorState()
const selectedEntities = selectionState.selectedEntities.value
const { t } = useTranslation()
const forceUpdate = useForceUpdate()
// force react to re-render upon any object changing
useEffect(() => {
forceUpdate()
}, [selectionState.objectChangeCounter])
const materialLibrary = getMaterialLibrary()
//rendering editor views for customization of element properties
let content
const world = Engine.instance.currentWorld
const lockedNode = editorState.lockPropertiesPanel.value
const multiEdit = selectedEntities.length > 1
let nodeEntity = lockedNode
? UUIDComponent.entitiesByUUID[lockedNode].value ?? lockedNode
: selectedEntities[selectedEntities.length - 1]
const isMaterial =
typeof nodeEntity === 'string' &&
(!!materialLibrary.materials[nodeEntity].value ||
Object.values(materialLibrary.materials.value)
.map(({ material }) => material.uuid)
.includes(nodeEntity))
const isObject3D = typeof nodeEntity === 'string' && !isMaterial
const node = isMaterial
? materialLibrary.materials[nodeEntity as string].value ??
Object.values(materialLibrary.materials.value).find(({ material }) => material.uuid === nodeEntity)
: isObject3D
? world.scene.getObjectByProperty('uuid', nodeEntity as string)
: nodeEntity
if (!nodeEntity || !node) {
content = {t('editor:properties.noNodeSelected')}
} else if (isObject3D) {
content = (
{/* @todo these types are incorrect */}
)
} else if (isMaterial) {
content = (
)
} else {
nodeEntity = nodeEntity as Entity
const components = getAllComponents(nodeEntity as Entity).filter((c) => EntityNodeEditor.has(c))
content = (
{components.map((c, i) => {
const Editor = EntityNodeEditor.get(c)!
// nodeEntity is used as key here to signal to React when the entity has changed,
// and to prevent state from being recycled between editor instances, which
// can cause hookstate to throw errors.
return (
)
})}
)
}
return {content}
}
export default PropertiesPanelContainer