Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
migrate prefab list (#10329)
Browse files Browse the repository at this point in the history
* migrate prefab list

* fix prefab ui

* make element list dynamic

* fix styling in elementlist

---------

Co-authored-by: aditya-mitra <[email protected]>
  • Loading branch information
JT00y and aditya-mitra authored Jun 10, 2024
1 parent 52c9134 commit af437b3
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 49 deletions.
3 changes: 2 additions & 1 deletion packages/client-core/i18n/en/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,8 @@
"openInNewTab": "Open URL in New Tab",
"deleteAsset": "Delete Asset",
"prefab": "Prefab",
"prefab-search": "Search Prefabs ...",
"prefabs": "Prefabs",
"prefabs-search": "Search Prefabs ...",
"components": "Components",
"components-search": "Search Components ...",
"component-detail": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,15 @@ import { cmdOrCtrlString } from '@etherealengine/editor/src/functions/utils'
import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
import { GLTFAssetState, GLTFSnapshotState } from '@etherealengine/engine/src/gltf/GLTFState'
import { PopoverPosition } from '@mui/material'
import { HiMagnifyingGlass, HiOutlinePlusCircle } from 'react-icons/hi2'
import { HierarchyPanelTab } from '..'
import Button from '../../../../../primitives/tailwind/Button'
import Input from '../../../../../primitives/tailwind/Input'
import ContextMenu from '../../../layout/ContextMenu'
import Popover from '../../../layout/Popover'
import { PopoverContext } from '../../../util/PopoverContext'
import ElementList from '../../Properties/elementList'
import HierarchyTreeNode, { HierarchyTreeNodeProps, RenameNodeData, getNodeElId } from '../node'

const uploadOptions = {
Expand All @@ -76,6 +81,7 @@ function HierarchyPanelContents(props: { sceneURL: string; rootEntityUUID: Entit
const [contextSelectedItem, setContextSelectedItem] = React.useState<undefined | HeirarchyTreeNodeType>(undefined)
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
const [anchorPosition, setAnchorPosition] = React.useState({ left: 0, top: 0 })
const [anchorPositionPop, setAnchorPositionPop] = React.useState<undefined | PopoverPosition>(undefined)
const [prevClickedNode, setPrevClickedNode] = useState<HeirarchyTreeNodeType | null>(null)
const onUpload = useUpload(uploadOptions)
const [renamingNode, setRenamingNode] = useState<RenameNodeData | null>(null)
Expand Down Expand Up @@ -435,31 +441,57 @@ function HierarchyPanelContents(props: { sceneURL: string; rootEntityUUID: Entit
{MemoTreeNode}
</FixedSizeList>
)

const panel = document.getElementById('propertiesPanel')
const anchorElButton = useHookstate<HTMLButtonElement | null>(null)
const open = !!anchorElButton.value
return (
<>
<div className="flex items-center gap-2 bg-theme-surface-main">
<Input
placeholder={t('common:components.search')}
value={searchHierarchy.value}
onChange={(event) => {
searchHierarchy.set(event.target.value)
<PopoverContext.Provider
value={{
handlePopoverClose: () => {
anchorElButton.set(null)
}
}}
>
<div className="flex items-center gap-2 bg-theme-surface-main">
<Input
placeholder={t('common:components.search')}
value={searchHierarchy.value}
onChange={(event) => {
searchHierarchy.set(event.target.value)
}}
className="m-1 rounded bg-theme-primary text-[#A3A3A3]"
startComponent={<HiMagnifyingGlass className="text-white" />}
/>
<Button
startIcon={<HiOutlinePlusCircle />}
variant="transparent"
rounded="none"
className="ml-auto w-32 bg-theme-highlight px-2 py-3"
size="small"
textContainerClassName="mx-0"
onClick={(event) => {
setAnchorPositionPop({ top: event.clientY - 10, left: panel?.getBoundingClientRect().left! + 10 })
anchorElButton.set(event.currentTarget)
}}
>
<span className="text-nowrap">{t('editor:hierarchy.lbl-addEntity')}</span>
</Button>
</div>
<Popover
open={open}
anchorEl={anchorElButton.value as any}
onClose={() => {
anchorElButton.set(null)
setAnchorPositionPop(undefined)
}}
className="m-1 rounded bg-theme-primary text-[#A3A3A3]"
startComponent={<HiMagnifyingGlass className="text-white" />}
/>
<Button
startIcon={<HiOutlinePlusCircle />}
variant="transparent"
rounded="none"
className="ml-auto w-32 bg-theme-highlight px-2 py-3"
size="small"
textContainerClassName="mx-0"
onClick={() => EditorControlFunctions.createObjectFromSceneElement()}
panelId={HierarchyPanelTab.id!}
anchorPosition={anchorPositionPop}
className="h-[60%] w-full min-w-[300px] overflow-y-auto"
>
<span className="text-nowrap">{t('editor:hierarchy.lbl-addEntity')}</span>
</Button>
</div>
<ElementList type="prefabs" />
</Popover>
</PopoverContext.Provider>
<div id="heirarchy-panel" className="h-5/6 overflow-hidden">
<AutoSizer onResize={HierarchyList}>{HierarchyList}</AutoSizer>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const EntityEditor = (props: { entityUUID: EntityUUID; multiEdit: boolean }) =>
anchorPosition={anchorPosition}
className="h-[60%] w-full min-w-[300px] overflow-y-auto"
>
<ElementList />
<ElementList type="components" />
</Popover>
<TransformPropertyGroup entity={entity} />
{components.map((c, i) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,32 @@ Ethereal Engine. All Rights Reserved.
*/

import { startCase } from 'lodash'
import React, { useEffect, useRef } from 'react'
import React, { useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'

import { Component } from '@etherealengine/ecs/src/ComponentFunctions'
import { getMutableState, getState, useHookstate, useMutableState } from '@etherealengine/hyperflux'
import { getState, useHookstate, useMutableState } from '@etherealengine/hyperflux'

import { PrefabShelfItem, PrefabShelfState } from '@etherealengine/editor/src/components/prefabs/PrefabEditors'
import { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
import { addMediaNode } from '@etherealengine/editor/src/functions/addMediaNode'
import { ComponentEditorsState } from '@etherealengine/editor/src/services/ComponentEditors'
import { ComponentShelfCategoriesState } from '@etherealengine/editor/src/services/ComponentShelfCategoriesState'
import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
import { GrStatusPlaceholder } from 'react-icons/gr'
import { IoIosArrowDown, IoIosArrowUp } from 'react-icons/io'
import { IoIosArrowDown, IoIosArrowUp, IoMdAddCircle } from 'react-icons/io'
import Button from '../../../../../primitives/tailwind/Button'
import Text from '../../../../../primitives/tailwind/Text'
import StringInput from '../../../input/String'
import { usePopoverContextClose } from '../../../util/PopoverContext'

type ElementsType = 'components' | 'prefabs'

export type SceneElementType = {
componentJsonID: string
label: string
Icon: any
Icon: JSX.Element
type: typeof ItemTypes.Component
}

Expand All @@ -57,15 +62,17 @@ const ComponentListItem = ({ item }: { item: Component }) => {
const jsonName = item.jsonID?.slice(3).replace('_', '-') || item.name

return (
<button
className="flex w-full items-center bg-theme-primary p-4 text-white"
<Button
variant="transparent"
fullWidth
className="w-full bg-theme-primary p-4 text-white"
onClick={() => {
const entities = SelectionState.getSelectedEntities()
EditorControlFunctions.addOrRemoveComponent(entities, item, true)
handleClosePopover()
}}
startIcon={<Icon className="h-6 w-6 text-white" />}
>
<Icon className="h-6 w-6 text-white" />
<div className="ml-4 w-full">
<Text className="text-subtitle1 block text-center text-theme-primary">
{startCase(jsonName.replace('-', ' ').toLowerCase())}
Expand All @@ -74,42 +81,80 @@ const ComponentListItem = ({ item }: { item: Component }) => {
{t(`editor:layout.assetGrid.component-detail.${jsonName}`, '')}
</Text>
</div>
</button>
</Button>
)
}

const PrefabListItem = ({ item }: { item: PrefabShelfItem }) => {
const handleClosePopover = usePopoverContextClose()

return (
<Button
variant="transparent"
fullWidth
className="w-full bg-theme-primary p-4 text-white"
onClick={() => {
const url = item.url
if (!url.length) {
EditorControlFunctions.createObjectFromSceneElement()
} else {
addMediaNode(url)
}
handleClosePopover()
}}
startIcon={<IoMdAddCircle className="h-6 w-6 text-white" />}
>
<div className="ml-4 w-full">
<Text className="text-subtitle1 block text-center text-theme-primary">{item.name}</Text>
<Text component="p" className="text-caption block text-center text-theme-secondary">
{item.detail}
</Text>
</div>
</Button>
)
}

const SceneElementListItem = ({
categoryTitle,
categoryItems,
isCollapsed
isCollapsed,
type
}: {
categoryTitle: string
categoryItems: Component[]
categoryItems: Component[] | PrefabShelfItem[]
isCollapsed: boolean
type: ElementsType
}) => {
const open = useHookstate(categoryTitle === 'Misc')
const open = useHookstate(false)
return (
<>
<button
<Button
variant="transparent"
fullWidth
className="w-full bg-theme-primary px-4 py-2 text-white"
textContainerClassName="text-start"
onClick={() => open.set((prev) => !prev)}
className="flex w-full cursor-pointer items-center justify-between bg-theme-primary px-4 py-2 text-white"
endIcon={isCollapsed || open.value ? <IoIosArrowUp /> : <IoIosArrowDown />}
>
<span>{categoryTitle}</span>
{isCollapsed || open.value ? <IoIosArrowUp /> : <IoIosArrowDown />}
</button>
{categoryTitle}
</Button>
<div className={isCollapsed || open.value ? '' : 'hidden'}>
<ul className="w-full bg-theme-primary">
{categoryItems.map((item) => (
<ComponentListItem key={item.jsonID || item.name} item={item} />
))}
{categoryItems.map((item: Component | PrefabShelfItem) =>
type === 'components' ? (
<ComponentListItem key={(item as Component).jsonID || item.name} item={item as Component} />
) : (
<PrefabListItem key={(item as PrefabShelfItem).url} item={item as PrefabShelfItem} />
)
)}
</ul>
</div>
</>
)
}

const useComponentShelfCategories = (search: string) => {
useHookstate(getMutableState(ComponentShelfCategoriesState)).value
useMutableState(ComponentShelfCategoriesState).value

if (!search) {
return Object.entries(getState(ComponentShelfCategoriesState))
Expand All @@ -125,12 +170,40 @@ const useComponentShelfCategories = (search: string) => {
.filter(([_, items]) => !!items.length)
}

export function ElementList() {
const usePrefabShelfCategories = (search: string): [string, PrefabShelfItem[]][] => {
const prefabState = useMutableState(PrefabShelfState).value
const prefabShelves = useMemo(() => {
const shelves: Record<string, PrefabShelfItem[]> = {}
for (const prefab of prefabState) {
shelves[prefab.category] ??= []
shelves[prefab.category].push(prefab)
}
return shelves
}, [prefabState])

if (!search) {
return Object.entries(prefabShelves)
}

const searchRegExp = new RegExp(search, 'gi')

return Object.entries(prefabShelves)
.map(([category, items]) => {
const filteredItems = items.filter((item) => item.name.match(searchRegExp)?.length)
return [category, filteredItems] as [string, PrefabShelfItem[]]
})
.filter(([_, items]) => !!items.length)
}

export function ElementList({ type }: { type: ElementsType }) {
const { t } = useTranslation()
const search = useHookstate({ local: '', query: '' })
const searchTimeout = useRef<ReturnType<typeof setTimeout> | null>(null)

const shelves = useComponentShelfCategories(search.query.value)
const shelves =
type === 'components'
? useComponentShelfCategories(search.query.value)
: usePrefabShelfCategories(search.query.value)
const inputReference = useRef<HTMLInputElement>(null)

useEffect(() => {
Expand All @@ -148,22 +221,24 @@ export function ElementList() {
return (
<>
<div className="h-auto w-full overflow-x-hidden overflow-y-scroll bg-theme-primary p-2">
<Text className="mb-1.5 w-full text-center uppercase text-white">
{t('editor:layout.assetGrid.components')}
</Text>
<Text className="mb-1.5 w-full text-center uppercase text-white">{t(`editor:layout.assetGrid.${type}`)}</Text>
<StringInput
placeholder={t('editor:layout.assetGrid.components-search')}
placeholder={t(`editor:layout.assetGrid.${type}-search`)}
value={search.local.value}
onChange={(val) => onSearch(val)}
inputRef={inputReference}
/>
</div>
{type === 'prefabs' && (
<PrefabListItem item={{ name: 'Empty', url: '', category: '', detail: 'Basic scene entity' }} />
)}
{shelves.map(([category, items]) => (
<SceneElementListItem
key={category}
categoryTitle={category}
categoryItems={items}
isCollapsed={!!search.query.value}
type={type}
/>
))}
</>
Expand Down

0 comments on commit af437b3

Please sign in to comment.