diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/components/MpMenuApp/MpMenuApp.tsx b/ext/cfx-ui/src/cfx/apps/mpMenu/components/MpMenuApp/MpMenuApp.tsx index 415aaf4f39..85494df86d 100644 --- a/ext/cfx-ui/src/cfx/apps/mpMenu/components/MpMenuApp/MpMenuApp.tsx +++ b/ext/cfx-ui/src/cfx/apps/mpMenu/components/MpMenuApp/MpMenuApp.tsx @@ -1,3 +1,4 @@ +import { observer } from "mobx-react-lite"; import { Outlet } from 'react-router-dom'; import { NavBar } from 'cfx/apps/mpMenu/parts/NavBar/NavBar'; import { AuthFlyout } from 'cfx/apps/mpMenu/parts/AuthFlyout/AuthFlyout'; @@ -7,13 +8,14 @@ import { LegacyConnectingModal } from 'cfx/apps/mpMenu/parts/LegacyConnectingMod import { LegacyUiMessageModal } from 'cfx/apps/mpMenu/parts/LegacyUiMessageModal/LegacyUiMessageModal'; import { ServerBoostModal } from 'cfx/apps/mpMenu/parts/ServerBoostModal/ServerBoostModal'; import { AcitivityItemMediaViewerProvider } from '../AcitivityItemMediaViewer/AcitivityItemMediaViewer.context'; +import { LegalAccepter } from "cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter"; +import { useLegalService } from "cfx/apps/mpMenu/services/legal/legal.service"; import { NavigationTracker } from './PageViewTracker'; import s from './MpMenuApp.module.scss'; -export function MpMenuApp() { +function MpMenuUI() { return ( - - + <> @@ -31,6 +33,22 @@ export function MpMenuApp() { - + ); } + +export const MpMenuApp = observer(function MpMenuApp() { + const legalService = useLegalService(); + + const mainUI = legalService.hasUserAccepted + ? + : ; + + return ( + + + + {mainUI} + + ); +}); diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/index.tsx b/ext/cfx-ui/src/cfx/apps/mpMenu/index.tsx index f998107c63..fad284fc84 100644 --- a/ext/cfx-ui/src/cfx/apps/mpMenu/index.tsx +++ b/ext/cfx-ui/src/cfx/apps/mpMenu/index.tsx @@ -40,8 +40,10 @@ import { IServersConnectService } from 'cfx/common/services/servers/serversConne import { Handle404 } from './pages/404'; import { registerHomeScreenServerList } from './services/servers/list/HomeScreenServerList.service'; import { registerSentryService } from './services/sentry/sentry.service'; +import { registerLegalService } from "./services/legal/legal.service"; import { SentryLogProvider } from './services/sentry/sentryLogProvider'; -import { animationFrame, idleCallback, timeout } from 'cfx/utils/async'; +import { timeout } from 'cfx/utils/async'; +import { shutdownLoadingSplash } from "./utils/loadingSplash"; startBrowserApp({ defineServices(container) { @@ -56,6 +58,8 @@ startBrowserApp({ MatomoAnalyticsProvider, ]); + registerLegalService(container); + registerSettingsService(container, { settings: GAME_SETTINGS, defaultSettingsCategoryId: DEFAULT_GAME_SETTINGS_CATEGORY_ID, @@ -142,29 +146,9 @@ startBrowserApp({ mpMenu.invokeNative('executeCommand', 'nui_devtools mpMenu'); } - mpMenu.invokeNative('getMinModeInfo'); + mpMenu.showGameWindow(); // Not using await here so app won't wait for this to end - timeout(1000).then(animationFrame).then(() => { - const $loader = document.getElementById('loader'); - if (!$loader) { - console.error('No #loader found, did it get deleted from index.html?'); - return; - } - - $loader.classList.add('hide'); - - const $loaderMask = $loader.querySelector('#loader-mask'); - if (!$loaderMask) { - console.error('No #loader-mask found, did it get deleted from index.html?'); - return; - } - - $loaderMask.addEventListener('animationend', async () => { - await idleCallback(1000); - - $loader.parentNode?.removeChild($loader); - }); - }); + timeout(1000).then(shutdownLoadingSplash); }, }); diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/mpMenu.ts b/ext/cfx-ui/src/cfx/apps/mpMenu/mpMenu.ts index 4e0b81a8ee..424492c562 100644 --- a/ext/cfx-ui/src/cfx/apps/mpMenu/mpMenu.ts +++ b/ext/cfx-ui/src/cfx/apps/mpMenu/mpMenu.ts @@ -60,7 +60,9 @@ class NicknameStore { export namespace mpMenu { export function invokeNative(native: string, arg = '') { - console.log('Invoking native', native, JSON.stringify(arg)); + if (__CFXUI_DEV__) { + console.log('Invoking native', native, JSON.stringify(arg)); + } nuiWindow.invokeNative(native, arg); } @@ -79,6 +81,21 @@ export namespace mpMenu { invokeNative('openUrl', url); } + let showGameWindowRequested = false; + export function showGameWindow() { + if (showGameWindowRequested) { + return; + } + + showGameWindowRequested = true; + + invokeNative('getMinModeInfo'); + } + + export function exit() { + invokeNative('exit'); + } + export const systemLanguages = [...new Set(nuiWindow.nuiSystemLanguages || ['en-us'])]; export const computerName = new AwaitableValue(''); @@ -152,7 +169,9 @@ export namespace mpMenu { window.addEventListener('message', (event: MessageEvent) => { - console.log('[WNDMSG]', event.data); + if (__CFXUI_DEV__) { + console.log('[WNDMSG]', event.data); + } const { data } = event; diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.module.scss b/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.module.scss new file mode 100644 index 0000000000..b207162a35 --- /dev/null +++ b/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.module.scss @@ -0,0 +1,15 @@ +.root { + width: min(max(calc(#{ui.viewport-width()} * .5), 500px), #{ui.viewport-width()}); + height: ui.viewport-height(); + + box-shadow: 0 0 0 2px ui.color-token('island-border') inset; + + @include ui.border-radius(); + background-color: ui.color-token('flyout-backdrop-background'); + + .iframe { + width: 100%; + height: 100%; + border: none; + } +} diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.tsx b/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.tsx new file mode 100644 index 0000000000..345efb2bde --- /dev/null +++ b/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.tsx @@ -0,0 +1,71 @@ +import { observer } from "mobx-react-lite"; +import { mpMenu } from "../../mpMenu"; +import { Pad } from "cfx/ui/Layout/Pad/Pad"; +import { Flex } from "cfx/ui/Layout/Flex/Flex"; +import { TextBlock } from "cfx/ui/Text/Text"; +import { FlexRestricter } from "cfx/ui/Layout/Flex/FlexRestricter"; +import { Button } from "cfx/ui/Button/Button"; +import { Icons } from "cfx/ui/Icons"; +import { Title } from "cfx/ui/Title/Title"; +import { Icon } from "cfx/ui/Icon/Icon"; +import { CurrentGameBrand } from "cfx/base/gameRuntime"; +import { useLegalService } from "cfx/apps/mpMenu/services/legal/legal.service"; +import s from './LegalAccepter.module.scss'; + +export const LegalAccepter = observer(function TOSAccepter() { + const legalService = useLegalService(); + + return ( +
+ {/* The reason for the reverse order here is so that the "I Accept" button captures focus first when/if user press Tab key */} + + + +
+ ); +}); diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/parts/NavBar/Exitter/Exitter.tsx b/ext/cfx-ui/src/cfx/apps/mpMenu/parts/NavBar/Exitter/Exitter.tsx index a467763217..ba32152f27 100644 --- a/ext/cfx-ui/src/cfx/apps/mpMenu/parts/NavBar/Exitter/Exitter.tsx +++ b/ext/cfx-ui/src/cfx/apps/mpMenu/parts/NavBar/Exitter/Exitter.tsx @@ -44,7 +44,7 @@ export function Exitter() { theme="default-blurred" size="large" text={$L('#ExitToDesktop')} - onClick={() => mpMenu.invokeNative('exit')} + onClick={mpMenu.exit} />