diff --git a/src/examplesRoute.tsx b/src/examplesRoute.tsx index e05dc0a..d67e0e9 100644 --- a/src/examplesRoute.tsx +++ b/src/examplesRoute.tsx @@ -5,56 +5,69 @@ import AvatarMocapEntry from './examples/avatarMocap' import AvatarTestEntry from './examples/avatarTest' import ComponentExamplesRoute, { subComponentExamples } from './examples/componentExamples/componentExamples' import GLTFViewer from './examples/gltf' +import ImmersiveAR from './examples/immersiveAR' import ImmersiveVR from './examples/immersiveVR' import MultipleScenesEntry from './examples/multipleScenes' -import Routes, { RouteData } from './sceneRoute' -import ImmersiveAR from './examples/immersiveAR' +import Routes, { RouteCategories } from './sceneRoute' -export const examples: RouteData[] = [ +export const examples: RouteCategories = [ + { + category: 'WebXR', + routes: [ + { + name: 'Immersive AR', + description: 'Immersive AR example', + entry: ImmersiveAR + }, + { + name: 'Immersive VR', + description: 'Immersive VR example', + entry: ImmersiveVR + } + ] + }, { - name: 'Components Example', - description: 'Component examples', - entry: ComponentExamplesRoute, - sub: subComponentExamples.map((sub) => ({ + category: 'Components', + routes: subComponentExamples.map((sub) => ({ name: sub.name, description: sub.description, - props: { Reactor: sub.Reactor } + entry: () => })) }, { - name: 'Avatar Mocap', - description: 'Avatar mocap example', - entry: AvatarMocapEntry - }, - { - name: 'Avatar Test', - description: 'Load many avatars', - entry: AvatarTestEntry - }, - { - name: 'GLTF Viewer', - description: 'Drag and drop GLTF files', - entry: GLTFViewer - }, - { - name: 'Multiple Scenes', - description: 'multiple scenes example', - entry: MultipleScenesEntry - }, - { - name: 'Immersive AR', - description: 'Immersive AR example', - entry: ImmersiveAR + category: 'Avatar', + routes: [ + { + name: 'Mocap', + description: 'Avatar mocap example', + entry: AvatarMocapEntry + }, + { + name: 'Test', + description: 'Load many avatars', + entry: AvatarTestEntry + } + ] }, { - name: 'Immersive VR', - description: 'Immersive VR example', - entry: ImmersiveVR + category: 'Scene', + routes: [ + { + name: 'GLTF Viewer', + description: 'Drag and drop GLTF files', + entry: GLTFViewer + }, + { + name: 'Multiple', + description: 'multiple scenes example', + entry: MultipleScenesEntry + } + ] } ] const ExampleRoutes = () => { - return + return } export default ExampleRoutes diff --git a/src/sceneRoute.tsx b/src/sceneRoute.tsx index 722ca3c..ed887cf 100644 --- a/src/sceneRoute.tsx +++ b/src/sceneRoute.tsx @@ -1,8 +1,9 @@ // @ts-ignore import styles from './sceneRoute.css?inline' -import React, { useEffect, useRef, useState } from 'react' +import React, { useEffect } from 'react' +import { SearchParamState } from '@etherealengine/client-core/src/common/services/RouterService' import { useLoadEngineWithScene, useNetwork } from '@etherealengine/client-core/src/components/World/EngineHooks' import { useLoadScene } from '@etherealengine/client-core/src/components/World/LoadLocationScene' import { useEngineCanvas } from '@etherealengine/client-core/src/hooks/useEngineCanvas' @@ -11,7 +12,13 @@ import { staticResourcePath } from '@etherealengine/common/src/schema.type.modul import { Entity, getComponent, setComponent } from '@etherealengine/ecs' import '@etherealengine/engine/src/EngineModule' import { GLTFAssetState } from '@etherealengine/engine/src/gltf/GLTFState' -import { useHookstate, useImmediateEffect, useMutableState } from '@etherealengine/hyperflux' +import { + getMutableState, + useHookstate, + useImmediateEffect, + useMutableState, + useReactiveRef +} from '@etherealengine/hyperflux' import { EngineState } from '@etherealengine/spatial/src/EngineState' import { CameraComponent } from '@etherealengine/spatial/src/camera/components/CameraComponent' import { CameraOrbitComponent } from '@etherealengine/spatial/src/camera/components/CameraOrbitComponent' @@ -19,7 +26,6 @@ import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHo import { InputComponent } from '@etherealengine/spatial/src/input/components/InputComponent' import Button from '@etherealengine/ui/src/primitives/tailwind/Button' import { HiChevronDoubleLeft, HiChevronDoubleRight } from 'react-icons/hi2' -import { SearchParamState } from '@etherealengine/client-core/src/common/services/RouterService' type Metadata = { name: string @@ -35,6 +41,8 @@ export type RouteData = Metadata & { sub?: SubRoute[] } +export type RouteCategories = Array<{ category: string; routes: RouteData[] }> + export const buttonStyle = { width: 'auto', height: '100%', @@ -59,6 +67,7 @@ const Header = (props: { header: string }) => { } export const useRouteScene = (projectName = 'ee-development-test-suite', sceneName = 'public/scenes/Examples.gltf') => { + const viewerEntity = useMutableState(EngineState).viewerEntity.value useLoadScene({ projectName: projectName, sceneName: sceneName }) useNetwork({ online: false }) useLoadEngineWithScene() @@ -67,7 +76,6 @@ export const useRouteScene = (projectName = 'ee-development-test-suite', sceneNa const gltfState = useMutableState(GLTFAssetState) const sceneEntity = useHookstate(undefined) - const viewerEntity = useMutableState(EngineState).viewerEntity.value useEffect(() => { if (!assetQuery.data[0]) return @@ -83,61 +91,37 @@ export const useRouteScene = (projectName = 'ee-development-test-suite', sceneNa setComponent(viewerEntity, InputComponent) getComponent(viewerEntity, CameraComponent).position.set(0, 3, 4) - // SearchParamState.set('spectate', '') + SearchParamState.set('spectate', '') }, [viewerEntity]) return sceneEntity } -const routeKey = 'route' -const subRouteKey = 'subroute' +const getPathForRoute = (category: string, name: string) => { + return (category.toLowerCase() + '_' + name.toLocaleLowerCase()).replace(' ', '_') +} -const Routes = (props: { routes: RouteData[]; header: string }) => { - const { routes, header } = props - const [currentRoute, setCurrentRoute] = useState(null as null | number) - const [currentSubRoute, setCurrentSubRoute] = useState(0) +const Routes = (props: { routeCategories: RouteCategories; header: string }) => { + const { routeCategories, header } = props + const currentRoute = useMutableState(SearchParamState).example.value + const categoriesShown = useHookstate({} as Record) const hidden = useHookstate(false) - const ref = useRef(null as null | HTMLDivElement) + const [ref, setRef] = useReactiveRef() useEngineCanvas(ref) - const onClick = (routeIndex: number) => { - setCurrentRoute(routeIndex) - setCurrentSubRoute(0) - } + const viewerEntity = useHookstate(getMutableState(EngineState).viewerEntity).value - const onSubClick = (subIndex: number) => { - setCurrentSubRoute(subIndex) + const onClick = (category: string, route: string) => { + SearchParamState.set('example', getPathForRoute(category, route)) } - useEffect(() => { - const queryString = window.location.search - const urlParams = new URLSearchParams(queryString) - const routeIndexStr = urlParams.get(routeKey) as any - if (routeIndexStr) { - const routeIndex = Number(routeIndexStr) - setCurrentRoute(routeIndex) - const subIndexStr = urlParams.get(subRouteKey) as any - if (subIndexStr) { - const subIndex = Number(subIndexStr) - setCurrentSubRoute(subIndex) - } - } - }, []) + const selectedRoute = routeCategories.flatMap((route) => + route.routes.filter((r) => getPathForRoute(route.category, r.name) === currentRoute) + )[0] - useEffect(() => { - if (currentRoute === null) return - const url = new URL(window.location.href) - url.searchParams.set(routeKey, currentRoute.toString()) - url.searchParams.set(subRouteKey, currentSubRoute.toString()) - window.history.pushState(null, '', url.toString()) - }, [currentRoute, currentSubRoute]) - - const selectedRoute = currentRoute !== null ? routes[currentRoute] : null - const selectedSub = selectedRoute && selectedRoute.sub && selectedRoute.sub[currentSubRoute] const Entry = selectedRoute && selectedRoute.entry - const subProps = selectedSub ? selectedSub.props : {} return ( <> @@ -160,43 +144,50 @@ const Routes = (props: { routes: RouteData[]; header: string }) => {
- {routes.map((route, index) => { - const title = route.name - const desc = route.description + {routeCategories.map((category, index) => { + const categoryShown = categoriesShown[category.category] return ( - -
onClick(index)} - > -
{title}
-
{desc}
+ +
+ {category.category} +
- {index === currentRoute && routes[currentRoute]?.sub && ( -
- {routes[currentRoute].sub?.map((sub, subIndex) => { - const subTitle = sub.name - const subDesc = sub.description - return ( + {categoryShown.value && + category.routes.map((route, index) => { + const title = route.name + const desc = route.description + const path = getPathForRoute(category.category, title) + return ( +
onSubClick(subIndex)} + className={path === currentRoute ? 'SelectedItemContainer' : 'RouteItemContainer'} + onClick={() => onClick(category.category, title)} > -
{subTitle}
-
{subDesc}
+
{title}
+
{desc}
- ) - })} -
- )} +
+ ) + })} ) })}
-
- {Entry && } +
+ {viewerEntity && Entry && }
)