diff --git a/packages/client-core/i18n/en/editor.json b/packages/client-core/i18n/en/editor.json index 629dc7b650..13e33ebe36 100755 --- a/packages/client-core/i18n/en/editor.json +++ b/packages/client-core/i18n/en/editor.json @@ -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": { diff --git a/packages/ui/src/components/editor/panels/Hierarchy/container/index.tsx b/packages/ui/src/components/editor/panels/Hierarchy/container/index.tsx index 9780de4381..79da362b70 100644 --- a/packages/ui/src/components/editor/panels/Hierarchy/container/index.tsx +++ b/packages/ui/src/components/editor/panels/Hierarchy/container/index.tsx @@ -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 = { @@ -76,6 +81,7 @@ function HierarchyPanelContents(props: { sceneURL: string; rootEntityUUID: Entit const [contextSelectedItem, setContextSelectedItem] = React.useState(undefined) const [anchorEl, setAnchorEl] = React.useState(null) const [anchorPosition, setAnchorPosition] = React.useState({ left: 0, top: 0 }) + const [anchorPositionPop, setAnchorPositionPop] = React.useState(undefined) const [prevClickedNode, setPrevClickedNode] = useState(null) const onUpload = useUpload(uploadOptions) const [renamingNode, setRenamingNode] = useState(null) @@ -435,31 +441,57 @@ function HierarchyPanelContents(props: { sceneURL: string; rootEntityUUID: Entit {MemoTreeNode} ) - + const panel = document.getElementById('propertiesPanel') + const anchorElButton = useHookstate(null) + const open = !!anchorElButton.value return ( <> -
- { - searchHierarchy.set(event.target.value) + { + anchorElButton.set(null) + } + }} + > +
+ { + searchHierarchy.set(event.target.value) + }} + className="m-1 rounded bg-theme-primary text-[#A3A3A3]" + startComponent={} + /> + +
+ { + anchorElButton.set(null) + setAnchorPositionPop(undefined) }} - className="m-1 rounded bg-theme-primary text-[#A3A3A3]" - startComponent={} - /> - -
+ + +
{HierarchyList}
diff --git a/packages/ui/src/components/editor/panels/Properties/container/index.tsx b/packages/ui/src/components/editor/panels/Properties/container/index.tsx index f505954c49..f14a8e5260 100644 --- a/packages/ui/src/components/editor/panels/Properties/container/index.tsx +++ b/packages/ui/src/components/editor/panels/Properties/container/index.tsx @@ -110,7 +110,7 @@ const EntityEditor = (props: { entityUUID: EntityUUID; multiEdit: boolean }) => anchorPosition={anchorPosition} className="h-[60%] w-full min-w-[300px] overflow-y-auto" > - + {components.map((c, i) => ( diff --git a/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx b/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx index b410b7c10a..5b4dab143c 100644 --- a/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx +++ b/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx @@ -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 } @@ -57,15 +62,17 @@ const ComponentListItem = ({ item }: { item: Component }) => { const jsonName = item.jsonID?.slice(3).replace('_', '-') || item.name return ( - + + ) +} + +const PrefabListItem = ({ item }: { item: PrefabShelfItem }) => { + const handleClosePopover = usePopoverContextClose() + + return ( + ) } 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 ( <> - + {categoryTitle} +
    - {categoryItems.map((item) => ( - - ))} + {categoryItems.map((item: Component | PrefabShelfItem) => + type === 'components' ? ( + + ) : ( + + ) + )}
@@ -109,7 +154,7 @@ const SceneElementListItem = ({ } const useComponentShelfCategories = (search: string) => { - useHookstate(getMutableState(ComponentShelfCategoriesState)).value + useMutableState(ComponentShelfCategoriesState).value if (!search) { return Object.entries(getState(ComponentShelfCategoriesState)) @@ -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 = {} + 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 | null>(null) - const shelves = useComponentShelfCategories(search.query.value) + const shelves = + type === 'components' + ? useComponentShelfCategories(search.query.value) + : usePrefabShelfCategories(search.query.value) const inputReference = useRef(null) useEffect(() => { @@ -148,22 +221,24 @@ export function ElementList() { return ( <>
- - {t('editor:layout.assetGrid.components')} - + {t(`editor:layout.assetGrid.${type}`)} onSearch(val)} inputRef={inputReference} />
+ {type === 'prefabs' && ( + + )} {shelves.map(([category, items]) => ( ))}