mirror of
https://github.com/CommunityOx/ox_lib.git
synced 2026-08-16 22:46:03 +01:00
feat(nui): Initial NUI files
This commit is contained in:
@@ -33,10 +33,14 @@ dependencies {
|
||||
'/onesync',
|
||||
}
|
||||
|
||||
ui_page 'web/build/index.html'
|
||||
|
||||
files {
|
||||
'init.lua',
|
||||
'imports/**/client.lua',
|
||||
'imports/**/shared.lua',
|
||||
'web/build/index.html',
|
||||
'web/build/**/*'
|
||||
}
|
||||
|
||||
shared_script 'resource/main.lua'
|
||||
|
||||
23
web/.gitignore
vendored
Normal file
23
web/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
24
web/craco.config.js
Normal file
24
web/craco.config.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const path = require("path");
|
||||
module.exports = {
|
||||
webpack: {
|
||||
configure: (webpackConfig) => {
|
||||
// Because CEF has issues with loading source maps properly atm,
|
||||
// lets use the best we can get in line with `eval-source-map`
|
||||
if (webpackConfig.mode === 'development' && process.env.IN_GAME_DEV) {
|
||||
webpackConfig.devtool = 'eval-source-map'
|
||||
webpackConfig.output.path = path.join(__dirname, 'build')
|
||||
}
|
||||
|
||||
return webpackConfig
|
||||
}
|
||||
},
|
||||
|
||||
devServer: (devServerConfig) => {
|
||||
if (process.env.IN_GAME_DEV) {
|
||||
// Used for in-game dev mode
|
||||
devServerConfig.devMiddleware.writeToDisk = true
|
||||
}
|
||||
|
||||
return devServerConfig
|
||||
}
|
||||
}
|
||||
54
web/package.json
Normal file
54
web/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.1.0",
|
||||
"homepage": "web/build",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^1.8.6",
|
||||
"@emotion/react": "11",
|
||||
"@emotion/styled": "11",
|
||||
"@testing-library/jest-dom": "^5.16.1",
|
||||
"@testing-library/react": "^12.1.2",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/node": "^16.11.18",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"framer-motion": "6",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "5.0.0",
|
||||
"typescript": "^4.5.5",
|
||||
"web-vitals": "^2.1.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env PUBLIC_URL=/ craco start",
|
||||
"start:game": "cross-env IN_GAME_DEV=1 craco start",
|
||||
"build": "rimraf build && craco build",
|
||||
"test": "craco test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^6.4.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"rimraf": "^3.0.2"
|
||||
}
|
||||
}
|
||||
23
web/public/index.html
Normal file
23
web/public/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<title>NUI React Boilerplate</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
9
web/src/components/App.tsx
Normal file
9
web/src/components/App.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<></>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
49
web/src/hooks/useNuiEvent.ts
Normal file
49
web/src/hooks/useNuiEvent.ts
Normal 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
19
web/src/index.css
Normal 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
18
web/src/index.tsx
Normal 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")
|
||||
);
|
||||
37
web/src/providers/VisibilityProvider.tsx
Normal file
37
web/src/providers/VisibilityProvider.tsx
Normal 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
1
web/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
8
web/src/theme/index.ts
Normal file
8
web/src/theme/index.ts
Normal 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})
|
||||
30
web/src/utils/debugData.ts
Normal file
30
web/src/utils/debugData.ts
Normal 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
28
web/src/utils/fetchNui.ts
Normal 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
6
web/src/utils/misc.ts
Normal 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 = () => {}
|
||||
26
web/tsconfig.json
Normal file
26
web/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
9872
web/yarn.lock
Normal file
9872
web/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user