From ee03b64f666a909f184dd548b9fba1ebce4cff0c Mon Sep 17 00:00:00 2001 From: Josh Field Date: Tue, 18 Jun 2024 07:20:33 +1000 Subject: [PATCH] App core tech debt componentization (#10395) * convert webapp injection, auth, notification to hooks/components and optimize engine loader * license * cleanup * refactor tw theme provider into hook * cleanup * update loading circles to loading views --------- Co-authored-by: Appaji <52322531+CITIZENDOT@users.noreply.github.com> --- ...tionService.ts => NotificationService.tsx} | 36 ++++++++++- .../src/common/services/ThemeService.tsx | 6 +- .../LoadWebappInjection.tsx} | 33 ++++++---- .../src/user/services/AuthService.ts | 17 +++++- packages/client/src/engine.tsx | 27 ++++++--- packages/client/src/main.tsx | 27 ++------- packages/client/src/pages/_app.tsx | 59 +++++------------- packages/client/src/pages/_app_tw.tsx | 60 +++++-------------- packages/client/src/route/public.tsx | 10 ---- packages/client/src/route/public_tw.tsx | 5 +- 10 files changed, 130 insertions(+), 150 deletions(-) rename packages/client-core/src/common/services/{NotificationService.ts => NotificationService.tsx} (65%) rename packages/client-core/src/{common/components/NotificationActions.tsx => components/LoadWebappInjection.tsx} (57%) diff --git a/packages/client-core/src/common/services/NotificationService.ts b/packages/client-core/src/common/services/NotificationService.tsx similarity index 65% rename from packages/client-core/src/common/services/NotificationService.ts rename to packages/client-core/src/common/services/NotificationService.tsx index 43887a90a7..8a7015fac6 100755 --- a/packages/client-core/src/common/services/NotificationService.ts +++ b/packages/client-core/src/common/services/NotificationService.tsx @@ -23,13 +23,15 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { SnackbarProvider, VariantType } from 'notistack' +import { SnackbarKey, SnackbarProvider, VariantType, closeSnackbar } from 'notistack' +import React, { CSSProperties, Fragment, useEffect, useRef } from 'react' import multiLogger from '@etherealengine/common/src/logger' import { AudioEffectPlayer } from '@etherealengine/engine/src/audio/systems/MediaSystem' -import { defineState, getState } from '@etherealengine/hyperflux' +import { defineState, getState, useMutableState } from '@etherealengine/hyperflux' -import { defaultAction } from '../components/NotificationActions' +import Icon from '@etherealengine/ui/src/primitives/mui/Icon' +import IconButton from '@etherealengine/ui/src/primitives/mui/IconButton' const logger = multiLogger.child({ component: 'client-core:Notification' }) @@ -45,6 +47,15 @@ export type NotificationOptions = { actionType?: keyof typeof NotificationActions } +export const defaultAction = (key: SnackbarKey, content?: React.ReactNode) => { + return ( + + {content} + closeSnackbar(key)} icon={} /> + + ) +} + export const NotificationActions = { default: defaultAction } @@ -63,3 +74,22 @@ export const NotificationService = { }) } } + +export const NotificationSnackbar = (props: { style?: CSSProperties }) => { + const notistackRef = useRef() + const notificationstate = useMutableState(NotificationState) + + useEffect(() => { + notificationstate.snackbar.set(notistackRef.current) + }, [notistackRef.current]) + + return ( + + ) +} diff --git a/packages/client-core/src/common/services/ThemeService.tsx b/packages/client-core/src/common/services/ThemeService.tsx index 480f79529c..8dca04d092 100644 --- a/packages/client-core/src/common/services/ThemeService.tsx +++ b/packages/client-core/src/common/services/ThemeService.tsx @@ -23,7 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import React, { useEffect } from 'react' +import { useEffect } from 'react' import { defineState, getMutableState, syncStateWithLocalStorage, useMutableState } from '@etherealengine/hyperflux' @@ -96,7 +96,7 @@ export const ThemeState = defineState({ extension: syncStateWithLocalStorage(['theme']) }) -export const ThemeProvider = ({ children }) => { +export const useThemeProvider = () => { const themeState = useMutableState(ThemeState) useEffect(() => { @@ -120,6 +120,4 @@ export const ThemeProvider = ({ children }) => { } } } - - return <>{children} } diff --git a/packages/client-core/src/common/components/NotificationActions.tsx b/packages/client-core/src/components/LoadWebappInjection.tsx similarity index 57% rename from packages/client-core/src/common/components/NotificationActions.tsx rename to packages/client-core/src/components/LoadWebappInjection.tsx index 62ee8f20a0..349e740ffa 100644 --- a/packages/client-core/src/common/components/NotificationActions.tsx +++ b/packages/client-core/src/components/LoadWebappInjection.tsx @@ -23,19 +23,32 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { SnackbarKey, useSnackbar } from 'notistack' -import React, { Fragment } from 'react' +import { NO_PROXY } from '@etherealengine/hyperflux' +import { loadWebappInjection } from '@etherealengine/projects/loadWebappInjection' +import LoadingView from '@etherealengine/ui/src/primitives/tailwind/LoadingView' +import { useHookstate } from '@hookstate/core' +import React, { useEffect } from 'react' +import { useTranslation } from 'react-i18next' -import Icon from '@etherealengine/ui/src/primitives/mui/Icon' -import IconButton from '@etherealengine/ui/src/primitives/mui/IconButton' +export const LoadWebappInjection = (props) => { + const { t } = useTranslation() -export const defaultAction = (key: SnackbarKey, content?: React.ReactNode) => { - const { closeSnackbar } = useSnackbar() + const projectComponents = useHookstate(null as null | any[]) + + useEffect(() => { + loadWebappInjection().then((result) => { + projectComponents.set(result) + }) + }, []) + + if (!projectComponents.value) return return ( - - {content} - closeSnackbar(key)} icon={} /> - + <> + {projectComponents.get(NO_PROXY)!.map((Component, i) => ( + + ))} + {props.children} + ) } diff --git a/packages/client-core/src/user/services/AuthService.ts b/packages/client-core/src/user/services/AuthService.ts index 4c4ee74ff4..fb37abaa6c 100755 --- a/packages/client-core/src/user/services/AuthService.ts +++ b/packages/client-core/src/user/services/AuthService.ts @@ -66,7 +66,8 @@ import { dispatchAction, getMutableState, getState, - syncStateWithLocalStorage + syncStateWithLocalStorage, + useHookstate } from '@etherealengine/hyperflux' import { API } from '../../API' @@ -759,3 +760,17 @@ function parseLoginDisplayCredential(credentials) { return { displayName, displayIcon } } + +export const useAuthenticated = () => { + const authState = useHookstate(getMutableState(AuthState)) + + useEffect(() => { + AuthService.doLoginAuto() + }, []) + + useEffect(() => { + Engine.instance.userID = authState.user.id.value + }, [authState.user.id]) + + return authState.isLoggedIn.value +} diff --git a/packages/client/src/engine.tsx b/packages/client/src/engine.tsx index 26bf4581db..d82d99ce76 100755 --- a/packages/client/src/engine.tsx +++ b/packages/client/src/engine.tsx @@ -23,11 +23,11 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import React, { createRef, Suspense } from 'react' +import React, { Suspense, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { API } from '@etherealengine/client-core/src/API' -import { LoadingCircle } from '@etherealengine/client-core/src/components/LoadingCircle' +import { BrowserRouter, history } from '@etherealengine/client-core/src/common/services/RouterService' import waitForClientAuthenticated from '@etherealengine/client-core/src/util/wait-for-client-authenticated' import { pipeLogs } from '@etherealengine/common/src/logger' import { Engine } from '@etherealengine/ecs/src/Engine' @@ -36,6 +36,7 @@ import { getMutableState } from '@etherealengine/hyperflux' import { EngineState } from '@etherealengine/spatial/src/EngineState' import { createEngine } from '@etherealengine/spatial/src/initializeEngine' +import LoadingView from '@etherealengine/ui/src/primitives/tailwind/LoadingView' import { initializei18n } from './util' const initializeLogs = async () => { @@ -53,12 +54,22 @@ initializeBrowser() API.createAPI() initializeLogs() -export default function ({ children, tailwind = false }): JSX.Element { - const ref = createRef() +export default function ({ children }): JSX.Element { const { t } = useTranslation() - return !tailwind ? ( - }>{children} - ) : ( - children + + useEffect(() => { + const urlSearchParams = new URLSearchParams(window.location.search) + const redirectUrl = urlSearchParams.get('redirectUrl') + if (redirectUrl) { + history.push(redirectUrl) + } + }, []) + + return ( + <> + + }>{children} + + ) } diff --git a/packages/client/src/main.tsx b/packages/client/src/main.tsx index 09cb4dddd6..ca452d5b1a 100755 --- a/packages/client/src/main.tsx +++ b/packages/client/src/main.tsx @@ -24,12 +24,11 @@ Ethereal Engine. All Rights Reserved. */ import { t } from 'i18next' -import React, { lazy, Suspense, useEffect } from 'react' +import React, { lazy, Suspense } from 'react' import { createRoot } from 'react-dom/client' import { Route, Routes } from 'react-router-dom' import ErrorBoundary from '@etherealengine/client-core/src/common/components/ErrorBoundary' -import { BrowserRouter, history } from '@etherealengine/client-core/src/common/services/RouterService' import { LoadingCircle } from '@etherealengine/client-core/src/components/LoadingCircle' import './pages/styles.scss' @@ -45,17 +44,9 @@ const AppPage = lazy(() => import('./pages/_app')) const TailwindPage = lazy(() => import('./pages/_app_tw')) const App = () => { - useEffect(() => { - const urlSearchParams = new URLSearchParams(window.location.search) - const redirectUrl = urlSearchParams.get('redirectUrl') - if (redirectUrl) { - history.push(redirectUrl) - } - }, []) - return ( - + {/* @todo - these are for backwards compatibility with non tailwind pages - they will be removed eventually */} { path="/location/*" element={ }> - - - + } /> @@ -74,9 +63,7 @@ const App = () => { path="/offline/*" element={ }> - - - + } /> @@ -86,14 +73,12 @@ const App = () => { path="/*" element={ - - - + } /> - + ) } diff --git a/packages/client/src/pages/_app.tsx b/packages/client/src/pages/_app.tsx index a36106bda1..52ad347f74 100755 --- a/packages/client/src/pages/_app.tsx +++ b/packages/client/src/pages/_app.tsx @@ -24,15 +24,13 @@ Ethereal Engine. All Rights Reserved. */ // import * as chapiWalletPolyfill from 'credential-handler-polyfill' -import { SnackbarProvider } from 'notistack' -import React, { useEffect, useRef, useState } from 'react' +import React, { useEffect } from 'react' import { initGA, logPageView } from '@etherealengine/client-core/src/common/analytics' -import { defaultAction } from '@etherealengine/client-core/src/common/components/NotificationActions' -import { NotificationState } from '@etherealengine/client-core/src/common/services/NotificationService' +import { NotificationSnackbar } from '@etherealengine/client-core/src/common/services/NotificationService' import Debug from '@etherealengine/client-core/src/components/Debug' import InviteToast from '@etherealengine/client-core/src/components/InviteToast' -import { AuthService, AuthState } from '@etherealengine/client-core/src/user/services/AuthService' +import { useAuthenticated } from '@etherealengine/client-core/src/user/services/AuthService' import '@etherealengine/client-core/src/util/GlobalStyle.css' @@ -40,10 +38,8 @@ import { StyledEngineProvider, Theme } from '@mui/material/styles' import { useTranslation } from 'react-i18next' import { LoadingCircle } from '@etherealengine/client-core/src/components/LoadingCircle' -import { Engine } from '@etherealengine/ecs/src/Engine' -import { useMutableState } from '@etherealengine/hyperflux' -import { loadWebappInjection } from '@etherealengine/projects/loadWebappInjection' +import { LoadWebappInjection } from '@etherealengine/client-core/src/components/LoadWebappInjection' import RouterComp from '../route/public' import { ThemeContextProvider } from './themeContext' @@ -54,36 +50,15 @@ declare module '@mui/styles/defaultTheme' { /** @deprecated see https://github.com/EtherealEngine/etherealengine/issues/6485 */ const AppPage = ({ route }: { route: string }) => { - const notistackRef = useRef() - const authState = useMutableState(AuthState) - const isLoggedIn = useMutableState(AuthState).isLoggedIn - const selfUser = authState.user - const [projectComponents, setProjectComponents] = useState | null>(null) - const notificationstate = useMutableState(NotificationState) + const isLoggedIn = useAuthenticated() const { t } = useTranslation() useEffect(() => { - AuthService.doLoginAuto() initGA() logPageView() }, []) - useEffect(() => { - notificationstate.snackbar.set(notistackRef.current) - }, [notistackRef.current]) - - useEffect(() => { - if (!isLoggedIn.value || projectComponents) return - loadWebappInjection().then((result) => { - setProjectComponents(result) - }) - }, [isLoggedIn]) - - useEffect(() => { - Engine.instance.userID = selfUser.id.value - }, [selfUser.id]) - - if (!isLoggedIn.value) { + if (!isLoggedIn) { return } @@ -91,23 +66,19 @@ const AppPage = ({ route }: { route: string }) => { <> - -
- - -
- {projectComponents && } - {projectComponents?.map((Component, i) => )} -
+ /> +
+ + +
+ + +
diff --git a/packages/client/src/pages/_app_tw.tsx b/packages/client/src/pages/_app_tw.tsx index 46f71f452d..274449b530 100755 --- a/packages/client/src/pages/_app_tw.tsx +++ b/packages/client/src/pages/_app_tw.tsx @@ -25,8 +25,7 @@ Ethereal Engine. All Rights Reserved. // import * as chapiWalletPolyfill from 'credential-handler-polyfill' -import { SnackbarProvider } from 'notistack' -import React, { useEffect, useRef, useState } from 'react' +import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { @@ -34,14 +33,12 @@ import { ClientSettingService } from '@etherealengine/client-core/src/admin/services/Setting/ClientSettingService' import { initGA, logPageView } from '@etherealengine/client-core/src/common/analytics' -import { defaultAction } from '@etherealengine/client-core/src/common/components/NotificationActions' -import { NotificationState } from '@etherealengine/client-core/src/common/services/NotificationService' -import { ThemeProvider } from '@etherealengine/client-core/src/common/services/ThemeService' +import { NotificationSnackbar } from '@etherealengine/client-core/src/common/services/NotificationService' +import { useThemeProvider } from '@etherealengine/client-core/src/common/services/ThemeService' import Debug from '@etherealengine/client-core/src/components/Debug' -import { AuthService, AuthState } from '@etherealengine/client-core/src/user/services/AuthService' -import { Engine } from '@etherealengine/ecs/src/Engine' +import { LoadWebappInjection } from '@etherealengine/client-core/src/components/LoadWebappInjection' +import { useAuthenticated } from '@etherealengine/client-core/src/user/services/AuthService' import { useMutableState } from '@etherealengine/hyperflux' -import { loadWebappInjection } from '@etherealengine/projects/loadWebappInjection' import LoadingView from '@etherealengine/ui/src/primitives/tailwind/LoadingView' import PublicRouter from '../route/public_tw' @@ -51,67 +48,40 @@ import '../themes/components.css' import '../themes/utilities.css' const AppPage = () => { - const authState = useMutableState(AuthState) - const isLoggedIn = useMutableState(AuthState).isLoggedIn - const selfUser = authState.user - const [projectComponents, setProjectComponents] = useState>([]) const { t } = useTranslation() + const isLoggedIn = useAuthenticated() useEffect(() => { - AuthService.doLoginAuto() initGA() logPageView() }, []) - useEffect(() => { - if (!isLoggedIn.value || projectComponents.length) return - loadWebappInjection().then((result) => { - setProjectComponents(result) - }) - }, [isLoggedIn]) - - useEffect(() => { - Engine.instance.userID = selfUser.id.value - }, [selfUser.id]) - - if (!/auth\/oauth/.test(location.pathname) && !isLoggedIn.value) { + if (!/auth\/oauth/.test(location.pathname) && !isLoggedIn) { return } return ( <> - {projectComponents && } - {projectComponents?.map((Component, i) => )} + + + ) } const TailwindPage = () => { - const notistackRef = useRef() - const notificationstate = useMutableState(NotificationState) const clientSettingState = useMutableState(AdminClientSettingsState) - - useEffect(() => { - notificationstate.snackbar.set(notistackRef.current) - }, [notistackRef.current]) - useEffect(() => { if (clientSettingState?.updateNeeded?.value) ClientSettingService.fetchClientSettings() }, [clientSettingState?.updateNeeded?.value]) + useThemeProvider() + return ( <> - - - - - - + + + ) } diff --git a/packages/client/src/route/public.tsx b/packages/client/src/route/public.tsx index 0d09b03696..2a98e4bbed 100644 --- a/packages/client/src/route/public.tsx +++ b/packages/client/src/route/public.tsx @@ -27,31 +27,21 @@ import React, { lazy, Suspense } from 'react' import { useTranslation } from 'react-i18next' import ErrorBoundary from '@etherealengine/client-core/src/common/components/ErrorBoundary' -import { useCustomRoutes } from '@etherealengine/client-core/src/common/services/RouterService' import { LoadingCircle } from '@etherealengine/client-core/src/components/LoadingCircle' -const $index = lazy(() => import('@etherealengine/client/src/pages')) const $offline = lazy(() => import('@etherealengine/client/src/pages/offline/offline')) -const $studio = lazy(() => import('@etherealengine/client/src/pages/editor/editor')) const $location = lazy(() => import('@etherealengine/client/src/pages/location/location')) /** @deprecated see https://github.com/EtherealEngine/etherealengine/issues/6485 */ function RouterComp({ route }: { route: string }) { - const customRoutes = useCustomRoutes() const { t } = useTranslation() let RouteElement switch (route) { - case 'index': - RouteElement = $index - break case 'offline': RouteElement = $offline break - case 'studio': - RouteElement = $studio - break case 'location': RouteElement = $location break diff --git a/packages/client/src/route/public_tw.tsx b/packages/client/src/route/public_tw.tsx index 5c7bc7c0e4..93149edfc7 100644 --- a/packages/client/src/route/public_tw.tsx +++ b/packages/client/src/route/public_tw.tsx @@ -29,8 +29,6 @@ import { Route, Routes } from 'react-router-dom' import ErrorBoundary from '@etherealengine/client-core/src/common/components/ErrorBoundary' import { useCustomRoutes } from '@etherealengine/client-core/src/common/services/RouterService' -import { AuthState } from '@etherealengine/client-core/src/user/services/AuthService' -import { getMutableState, useHookstate } from '@etherealengine/hyperflux' import LoadingView from '@etherealengine/ui/src/primitives/tailwind/LoadingView' import $404 from '../pages/404' @@ -40,9 +38,8 @@ const $custom = lazy(() => import('@etherealengine/client/src/route/customRoutes function PublicRouter() { const customRoutes = useCustomRoutes() - const isLoggedIn = useHookstate(getMutableState(AuthState).isLoggedIn) - if (!/auth\/oauth/.test(location.pathname) && (!customRoutes.length || !isLoggedIn.value)) { + if (!/auth\/oauth/.test(location.pathname) && !customRoutes.length) { return }