feat(nui): Initial NUI files

This commit is contained in:
Luke
2022-03-13 22:30:57 +01:00
parent 2ce934c41a
commit 5314b59e8b
17 changed files with 10231 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
const App: React.FC = () => {
return (
<>
<></>
</>
);
};
export default App;

View File

@@ -0,0 +1,49 @@
import {MutableRefObject, useEffect, useRef} from "react";
import {noop} from "../utils/misc";
interface NuiMessageData<T = unknown> {
action: string;
data: T;
}
type NuiHandlerSignature<T> = (data: T) => void;
/**
* A hook that manage events listeners for receiving data from the client scripts
* @param action The specific `action` that should be listened for.
* @param handler The callback function that will handle data relayed by this hook
*
* @example
* useNuiEvent<{visibility: true, wasVisible: 'something'}>('setVisible', (data) => {
* // whatever logic you want
* })
*
**/
export const useNuiEvent = <T = any>(
action: string,
handler: (data: T) => void
) => {
const savedHandler: MutableRefObject<NuiHandlerSignature<T>> = useRef(noop);
// Make sure we handle for a reactive handler
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const eventListener = (event: MessageEvent<NuiMessageData<T>>) => {
const { action: eventAction, data } = event.data;
if (savedHandler.current) {
if (eventAction === action) {
savedHandler.current(data);
}
}
};
window.addEventListener("message", eventListener);
// Remove Event Listener on component cleanup
return () => window.removeEventListener("message", eventListener);
}, [action]);
};

19
web/src/index.css Normal file
View File

@@ -0,0 +1,19 @@
body {
background: none !important;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
height: 100vh;
}
#root {
height: 100%;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}

18
web/src/index.tsx Normal file
View File

@@ -0,0 +1,18 @@
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./components/App";
import { VisibilityProvider } from "./providers/VisibilityProvider";
import { ChakraProvider } from "@chakra-ui/react";
import { theme } from "./theme";
ReactDOM.render(
<React.StrictMode>
<VisibilityProvider>
<ChakraProvider theme={theme}>
<App />
</ChakraProvider>
</VisibilityProvider>
</React.StrictMode>,
document.getElementById("root")
);

View File

@@ -0,0 +1,37 @@
import React, { Context, createContext, useContext, useState } from "react";
import { useNuiEvent } from "../hooks/useNuiEvent";
const VisibilityCtx = createContext<VisibilityProviderValue | null>(null);
interface VisibilityProviderValue {
setVisible: (visible: boolean) => void;
visible: boolean;
}
// This should be mounted at the top level of your application, it is currently set to
// apply a CSS visibility value. If this is non-performant, this should be customized.
export const VisibilityProvider: React.FC = ({ children }) => {
const [visible, setVisible] = useState(false);
useNuiEvent<boolean>("setVisible", setVisible);
return (
<VisibilityCtx.Provider
value={{
visible,
setVisible,
}}
>
<div
style={{ visibility: visible ? "visible" : "hidden", height: "100%" }}
>
{children}
</div>
</VisibilityCtx.Provider>
);
};
export const useVisibility = () =>
useContext<VisibilityProviderValue>(
VisibilityCtx as Context<VisibilityProviderValue>
);

1
web/src/react-app-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

8
web/src/theme/index.ts Normal file
View File

@@ -0,0 +1,8 @@
import { extendTheme, type ThemeConfig } from "@chakra-ui/react";
const config: ThemeConfig = {
initialColorMode: 'dark',
useSystemColorMode: false
}
export const theme = extendTheme({config})

View File

@@ -0,0 +1,30 @@
import {isEnvBrowser} from "./misc";
interface DebugEvent<T = any> {
action: string;
data: T;
}
/**
* Emulates dispatching an event using SendNuiMessage in the lua scripts.
* This is used when developing in browser
*
* @param events - The event you want to cover
* @param timer - How long until it should trigger (ms)
*/
export const debugData = <P>(events: DebugEvent<P>[], timer = 1000): void => {
if (process.env.NODE_ENV === "development" && isEnvBrowser()) {
for (const event of events) {
setTimeout(() => {
window.dispatchEvent(
new MessageEvent("message", {
data: {
action: event.action,
data: event.data,
},
})
);
}, timer);
}
}
};

28
web/src/utils/fetchNui.ts Normal file
View File

@@ -0,0 +1,28 @@
/**
* Simple wrapper around fetch API tailored for CEF/NUI use. This abstraction
* can be extended to include AbortController if needed or if the response isn't
* JSON. Tailor it to your needs.
*
* @param eventName - The endpoint eventname to target
* @param data - Data you wish to send in the NUI Callback
*
* @return returnData - A promise for the data sent back by the NuiCallbacks CB argument
*/
export async function fetchNui<T = any>(eventName: string, data?: any): Promise<T> {
const options = {
method: 'post',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: JSON.stringify(data),
};
const resourceName = (window as any).GetParentResourceName ? (window as any).GetParentResourceName() : 'nui-frame-app';
const resp = await fetch(`https://${resourceName}/${eventName}`, options);
const respFormatted = await resp.json()
return respFormatted
}

6
web/src/utils/misc.ts Normal file
View File

@@ -0,0 +1,6 @@
// Will return whether the current environment is in a regular browser
// and not CEF
export const isEnvBrowser = (): boolean => !(window as any).invokeNative
// Basic no operation function
export const noop = () => {}