diff --git a/packages/client-core/src/admin/adminRoutes.tsx b/packages/client-core/src/admin/adminRoutes.tsx index 32ecf189c1..78aa65ebbd 100644 --- a/packages/client-core/src/admin/adminRoutes.tsx +++ b/packages/client-core/src/admin/adminRoutes.tsx @@ -1,6 +1,6 @@ import { t } from 'i18next' import React, { lazy, Suspense, useEffect } from 'react' -import { Navigate, Route, Routes } from 'react-router-dom' +import { Navigate, Route, Routes, useLocation } from 'react-router-dom' import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' import { EngineActions } from '@etherealengine/engine/src/ecs/classes/EngineState' @@ -21,7 +21,8 @@ const AdminSystemInjection = { systemLoader: () => Promise.resolve({ default: AdminSystem }) } as const -const ProtectedRoutes = () => { +const AdminRoutes = () => { + const location = useLocation() const admin = useAuthState().user let allowedRoutes = { @@ -46,6 +47,7 @@ const ProtectedRoutes = () => { useEffect(() => { initSystems([AdminSystemInjection]).then(async () => { + // @ts-ignore dispatchAction(EngineActions.initializeEngine({ initialised: true })) }) }, []) @@ -67,7 +69,7 @@ const ProtectedRoutes = () => { return ( - }> + }> } /> {} />} @@ -77,4 +79,4 @@ const ProtectedRoutes = () => { ) } -export default ProtectedRoutes +export default AdminRoutes diff --git a/packages/client-core/src/admin/components/Avatars/index.tsx b/packages/client-core/src/admin/components/Avatars/index.tsx index a2e028f56a..b385c55d3e 100644 --- a/packages/client-core/src/admin/components/Avatars/index.tsx +++ b/packages/client-core/src/admin/components/Avatars/index.tsx @@ -4,8 +4,8 @@ import { useTranslation } from 'react-i18next' import ConfirmDialog from '@etherealengine/client-core/src/common/components/ConfirmDialog' import { AvatarClientModule } from '@etherealengine/engine/src/avatar/AvatarClientModule' import { AvatarCommonModule } from '@etherealengine/engine/src/avatar/AvatarCommonModule' -import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' import { initSystems, unloadSystems } from '@etherealengine/engine/src/ecs/functions/SystemFunctions' +import { RendererModule } from '@etherealengine/engine/src/renderer/RendererModule' import { SceneClientModule } from '@etherealengine/engine/src/scene/SceneClientModule' import { SceneCommonModule } from '@etherealengine/engine/src/scene/SceneCommonModule' import { TransformModule } from '@etherealengine/engine/src/transform/TransformModule' @@ -31,6 +31,7 @@ const Avatar = () => { useEffect(() => { const systems = [ ...TransformModule(), + ...RendererModule(), ...SceneCommonModule(), ...SceneClientModule(), ...AvatarCommonModule(), diff --git a/packages/client-core/src/components/FullscreenContainer.tsx b/packages/client-core/src/components/FullscreenContainer.tsx index a5d421a1c1..b226e60f85 100644 --- a/packages/client-core/src/components/FullscreenContainer.tsx +++ b/packages/client-core/src/components/FullscreenContainer.tsx @@ -13,8 +13,10 @@ export const FullscreenContainer = React.forwardRef((props: Props, ref: any) => useEffect(() => { if (ref?.current) { const canvas = document.getElementById('engine-renderer-canvas')! - document.body.removeChild(canvas) - ref.current.appendChild(canvas) + if (document.body.contains(canvas)) { + document.body.removeChild(canvas) + ref.current.appendChild(canvas) + } } }, [ref]) diff --git a/packages/client/src/engine.tsx b/packages/client/src/engine.tsx index 190ea4331e..485be47de6 100755 --- a/packages/client/src/engine.tsx +++ b/packages/client/src/engine.tsx @@ -1,4 +1,4 @@ -import React, { createRef, lazy, Suspense } from 'react' +import React, { createRef, Suspense } from 'react' import { useTranslation } from 'react-i18next' import { API } from '@etherealengine/client-core/src/API' @@ -21,16 +21,12 @@ setupEngineActionSystems() initializeBrowser() API.createAPI() -const AppPage = lazy(() => import('./pages/_app')) - -export default function () { +export default function ({ children }) { const ref = createRef() const { t } = useTranslation() return ( - }> - - + }>{children} ) } diff --git a/packages/client/src/main.tsx b/packages/client/src/main.tsx index edc29412b7..d9076e84e8 100755 --- a/packages/client/src/main.tsx +++ b/packages/client/src/main.tsx @@ -1,18 +1,48 @@ import { t } from 'i18next' import React, { lazy, Suspense } from 'react' import { createRoot } from 'react-dom/client' +import { BrowserRouter, Route, Routes } from 'react-router-dom' -import { LoadingCircle } from '@etherealengine/client-core/src/components/LoadingCircle' //@ts-ignore +import ErrorBoundary from '@etherealengine/client-core/src/common/components/ErrorBoundary' +import { LoadingCircle } from '@etherealengine/client-core/src/components/LoadingCircle' +// @ts-ignore ;(globalThis as any).process = { env: { ...(import.meta as any).env, APP_ENV: (import.meta as any).env.MODE } } const Engine = lazy(() => import('./engine')) +const AppPage = lazy(() => import('./pages/_app')) +const AdminPage = lazy(() => import('./pages/admin')) const App = () => { return ( - }> - - + + + + }> + + + + + } + /> + }> + + + + + } + /> + + + ) } diff --git a/packages/client/src/pages/_app.tsx b/packages/client/src/pages/_app.tsx index 31b0a16dc5..e09a29fc4c 100755 --- a/packages/client/src/pages/_app.tsx +++ b/packages/client/src/pages/_app.tsx @@ -1,7 +1,7 @@ // import * as chapiWalletPolyfill from 'credential-handler-polyfill' import { SnackbarProvider } from 'notistack' import React, { createRef, useCallback, useEffect, useRef, useState } from 'react' -import { BrowserRouter, useLocation } from 'react-router-dom' +import { useLocation } from 'react-router-dom' import { ClientSettingService, @@ -63,7 +63,7 @@ declare module '@mui/styles/defaultTheme' { interface DefaultTheme extends Theme {} } -const App = (): any => { +const AppPage = (): any => { const notistackRef = useRef() const authState = useAuthState() const selfUser = authState.user @@ -91,6 +91,7 @@ const App = (): any => { useEffect(() => { const receptor = (action): any => { + // @ts-ignore matches(action).when(NotificationAction.notify.matches, (action) => { AudioEffectPlayer.instance.play(AudioEffectPlayer.SOUNDS.alert, 0.5) notistackRef.current?.enqueueSnackbar(action.message, { @@ -260,12 +261,4 @@ const App = (): any => { ) } -const AppPage = () => { - return ( - - - - ) -} - export default AppPage diff --git a/packages/client/src/pages/admin.tsx b/packages/client/src/pages/admin.tsx index 8c887398d0..73e2e75a9f 100755 --- a/packages/client/src/pages/admin.tsx +++ b/packages/client/src/pages/admin.tsx @@ -1,9 +1,8 @@ // import * as chapiWalletPolyfill from 'credential-handler-polyfill' import { SnackbarProvider } from 'notistack' -import React, { createRef, useCallback, useEffect, useRef, useState } from 'react' -import { BrowserRouter, useLocation } from 'react-router-dom' +import React, { lazy, useCallback, useEffect, useRef, useState } from 'react' +import { useLocation } from 'react-router-dom' -import AdminRoutes from '@etherealengine/client-core/src/admin/adminRoutes' import { ClientSettingService, useClientSettingState @@ -22,6 +21,8 @@ import { loadWebappInjection } from '@etherealengine/projects/loadWebappInjectio import { StyledEngineProvider, Theme, ThemeProvider } from '@mui/material/styles' +import AdminRouterComp from '../route/admin' + import './styles.scss' import { @@ -29,6 +30,7 @@ import { useCoilSettingState } from '@etherealengine/client-core/src/admin/services/Setting/CoilSettingService' import { API } from '@etherealengine/client-core/src/API' +import ErrorBoundary from '@etherealengine/client-core/src/common/components/ErrorBoundary' import UIDialog from '@etherealengine/client-core/src/common/components/UIDialog' import { AppThemeServiceReceptor, @@ -62,7 +64,7 @@ declare module '@mui/styles/defaultTheme' { interface DefaultTheme extends Theme {} } -const App = (): any => { +const AdminPage = (): any => { const notistackRef = useRef() const authState = useAuthState() const selfUser = authState.user @@ -90,6 +92,7 @@ const App = (): any => { useEffect(() => { const receptor = (action): any => { + // @ts-ignore matches(action).when(NotificationAction.notify.matches, (action) => { AudioEffectPlayer.instance.play(AudioEffectPlayer.SOUNDS.alert, 0.5) notistackRef.current?.enqueueSnackbar(action.message, { @@ -248,7 +251,7 @@ const App = (): any => { - + {projectComponents.map((Component, i) => ( ))} @@ -259,12 +262,4 @@ const App = (): any => { ) } -const AppPage = () => { - return ( - - - - ) -} - -export default AppPage +export default AdminPage diff --git a/packages/client/src/route/admin.tsx b/packages/client/src/route/admin.tsx new file mode 100644 index 0000000000..d5fe9c9377 --- /dev/null +++ b/packages/client/src/route/admin.tsx @@ -0,0 +1,132 @@ +import React, { lazy, Suspense, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Route, Routes, useLocation, useNavigate } from 'react-router-dom' + +import { + AuthSettingsService, + AuthSettingsServiceReceptor, + useAuthSettingState +} from '@etherealengine/client-core/src/admin/services/Setting/AuthSettingService' +import { + ClientSettingsServiceReceptor, + useClientSettingState +} from '@etherealengine/client-core/src/admin/services/Setting/ClientSettingService' +import ErrorBoundary from '@etherealengine/client-core/src/common/components/ErrorBoundary' +import { AppLoadingServiceReceptor } from '@etherealengine/client-core/src/common/services/AppLoadingService' +import { AppServiceReceptor } from '@etherealengine/client-core/src/common/services/AppService' +import { DialogServiceReceptor } from '@etherealengine/client-core/src/common/services/DialogService' +import { MediaInstanceConnectionServiceReceptor } from '@etherealengine/client-core/src/common/services/MediaInstanceConnectionService' +import { ProjectServiceReceptor } from '@etherealengine/client-core/src/common/services/ProjectService' +import { + RouterServiceReceptor, + RouterState, + useRouter +} from '@etherealengine/client-core/src/common/services/RouterService' +import { LoadingCircle } from '@etherealengine/client-core/src/components/LoadingCircle' +import { FriendServiceReceptor } from '@etherealengine/client-core/src/social/services/FriendService' +import { InviteService, InviteServiceReceptor } from '@etherealengine/client-core/src/social/services/InviteService' +import { LocationServiceReceptor } from '@etherealengine/client-core/src/social/services/LocationService' +import { AuthService, AuthServiceReceptor } from '@etherealengine/client-core/src/user/services/AuthService' +import { AvatarServiceReceptor } from '@etherealengine/client-core/src/user/services/AvatarService' +import { addActionReceptor, getState, removeActionReceptor, useHookstate } from '@etherealengine/hyperflux' + +import $404 from '../pages/404' +import $503 from '../pages/503' +import { CustomRoute, getCustomRoutes } from './getCustomRoutes' + +const $admin = lazy(() => import('@etherealengine/client-core/src/admin/adminRoutes')) + +function AdminRouterComp() { + const [customRoutes, setCustomRoutes] = useState(null as any as CustomRoute[]) + const clientSettingsState = useClientSettingState() + const authSettingsState = useAuthSettingState() + const location = useLocation() + const navigate = useNavigate() + const [routesReady, setRoutesReady] = useState(false) + const routerState = useHookstate(getState(RouterState)) + const route = useRouter() + const { t } = useTranslation() + + InviteService.useAPIListeners() + + useEffect(() => { + addActionReceptor(RouterServiceReceptor) + addActionReceptor(ClientSettingsServiceReceptor) + addActionReceptor(AuthSettingsServiceReceptor) + addActionReceptor(AuthServiceReceptor) + addActionReceptor(AvatarServiceReceptor) + addActionReceptor(InviteServiceReceptor) + addActionReceptor(LocationServiceReceptor) + addActionReceptor(DialogServiceReceptor) + addActionReceptor(AppLoadingServiceReceptor) + addActionReceptor(AppServiceReceptor) + addActionReceptor(ProjectServiceReceptor) + addActionReceptor(MediaInstanceConnectionServiceReceptor) + addActionReceptor(FriendServiceReceptor) + + // Oauth callbacks may be running when a guest identity-provider has been deleted. + // This would normally cause doLoginAuto to make a guest user, which we do not want. + // Instead, just skip it on oauth callbacks, and the callback handler will log them in. + // The client and auth settigns will not be needed on these routes + if (!/auth\/oauth/.test(location.pathname)) { + AuthService.doLoginAuto() + AuthSettingsService.fetchAuthSetting() + } + getCustomRoutes().then((routes) => { + setCustomRoutes(routes) + }) + + return () => { + removeActionReceptor(RouterServiceReceptor) + removeActionReceptor(ClientSettingsServiceReceptor) + removeActionReceptor(AuthSettingsServiceReceptor) + removeActionReceptor(AuthServiceReceptor) + removeActionReceptor(AvatarServiceReceptor) + removeActionReceptor(InviteServiceReceptor) + removeActionReceptor(LocationServiceReceptor) + removeActionReceptor(DialogServiceReceptor) + removeActionReceptor(AppServiceReceptor) + removeActionReceptor(AppLoadingServiceReceptor) + removeActionReceptor(ProjectServiceReceptor) + removeActionReceptor(MediaInstanceConnectionServiceReceptor) + removeActionReceptor(FriendServiceReceptor) + } + }, []) + + useEffect(() => { + if (location.pathname !== routerState.pathname.value) { + route(location.pathname) + } + }, [location.pathname]) + + useEffect(() => { + if (location.pathname !== routerState.pathname.value) { + navigate(routerState.pathname.value) + } + }, [routerState.pathname]) + + useEffect(() => { + // For the same reason as above, we will not need to load the client and auth settings for these routes + if (/auth\/oauth/.test(location.pathname) && customRoutes) return setRoutesReady(true) + if (clientSettingsState.client.value.length && authSettingsState.authSettings.value.length && customRoutes) + return setRoutesReady(true) + }, [clientSettingsState.client.length, authSettingsState.authSettings.length, customRoutes]) + + if (!routesReady) { + return ( + + + + ) + } + + return ( + + }> + <$admin /> + + + ) +} + +export default AdminRouterComp diff --git a/packages/client/src/route/public.tsx b/packages/client/src/route/public.tsx index b3c3404e23..536d868268 100644 --- a/packages/client/src/route/public.tsx +++ b/packages/client/src/route/public.tsx @@ -1,4 +1,4 @@ -import React, { lazy, Suspense, useCallback, useEffect, useState } from 'react' +import React, { lazy, Suspense, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { Route, Routes, useLocation, useNavigate } from 'react-router-dom' @@ -132,7 +132,11 @@ function RouterComp() { }> - } /> + c.route !== '/admin')} />} + /> } /> {/* default to allowing admin access regardless */} } />