This commit is contained in:
2024-06-09 22:43:57 -05:00
parent f16a32b63e
commit 1259e726bb
17 changed files with 4611 additions and 368 deletions

4259
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -20,9 +20,12 @@
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@vitejs/plugin-react": "^4.2.0",
"autoprefixer": "^10.4.19",
"eslint": "^8.54.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.2.2",
"vite": "^5.0.0"
}

6
web/postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
web/src/assets/beep.mp3 Normal file

Binary file not shown.

BIN
web/src/assets/denied.mp3 Normal file

Binary file not shown.

BIN
web/src/assets/unlock.mp3 Normal file

Binary file not shown.

View File

@ -1,26 +0,0 @@
.nui-wrapper {
text-align: center;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
pre {
counter-reset:line-numbering;
background:#2c3e50;
padding:12px 0px 14px 0;
color:#ecf0f1;
line-height:140%;
}
.popup-thing {
background: #282c34;
border-radius: 10px;
width: 500px;
height: 400px;
display: flex;
justify-content: center;
align-items: center;
color: white;
}

View File

@ -1,66 +1,153 @@
import React, { useState } from "react";
import "./App.css";
import { debugData } from "../utils/debugData";
import { fetchNui } from "../utils/fetchNui";
import React, { useState } from 'react';
import { debugData } from '../utils/debugData';
import { fetchNui } from '../utils/fetchNui';
// Import Audios
import beep from '../assets/beep.mp3';
import deined from '../assets/denied.mp3';
import unlock from '../assets/unlock.mp3';
import { useNuiEvent } from '../hooks/useNuiEvent';
// This will set the NUI to visible if we are
// developing in browser
debugData([
{
action: "setVisible",
data: true,
},
{
action: 'setVisible',
data: true,
},
]);
interface ReturnClientDataCompProps {
data: unknown;
}
const ReturnClientDataComp: React.FC<ReturnClientDataCompProps> = ({
data,
}) => (
<>
<h5>Returned Data:</h5>
<pre>
<code>{JSON.stringify(data, null)}</code>
</pre>
</>
);
interface ReturnData {
x: number;
y: number;
z: number;
}
const playKeypadSound = () => {
const audio = new Audio(beep);
audio.play();
};
const App: React.FC = () => {
const [clientData, setClientData] = useState<ReturnData | null>(null);
const [keypadState, setKeypadState] = useState<string>('');
const [code, setCode] = useState<string>('1234');
const [keypadId, setKeypadId] = useState<string>('');
const [codeValidated, setCodeValidated] = useState<boolean>(false);
const [codeState, setCodeState] = useState<boolean>(false);
const [submitButtonEnabled, setSubmitButtonEnabled] =
useState<boolean>(false);
const handleGetClientData = () => {
fetchNui<ReturnData>("getClientData")
.then((retData) => {
console.log("Got return data from client scripts:");
console.dir(retData);
setClientData(retData);
})
.catch((e) => {
console.error("Setting mock data due to error", e);
setClientData({ x: 500, y: 300, z: 200 });
});
};
// Listen for our "uiCodeSetup" event
// and set the code state
useNuiEvent<{ doorId: string; code: string }>(
'sticks_keypad:uiInit',
(data) => {
console.log('[sticks_keypad:nui::setDoorId] uiInit: init...');
setCode(data.code);
setKeypadId(data.doorId);
return (
<div className="nui-wrapper">
<div className="popup-thing">
<div>
<h1>This is the NUI Popup!</h1>
<p>Exit with the escape key</p>
<button onClick={handleGetClientData}>Get Client Data</button>
{clientData && <ReturnClientDataComp data={clientData} />}
console.log(
'[sticks_keypad:nui::setDoorId] uiInit: doorId: ',
data.doorId,
);
console.log('[sticks_keypad:nui::setDoorId] uiInit done');
},
);
async function onCodeSubmit(data: string) {
// Load both the denied and unlock audio
const deniedAudio = new Audio(deined);
const unlockAudio = new Audio(unlock);
// Check if the data is equal to the code
if (data === code) {
// Play the unlock audio
await unlockAudio.play();
// Set the code state to true
setCodeState(true);
// Send the data to the server
fetchNui('sticks_keypad:codeSubmitSuccess', {
code: data,
doorId: keypadId,
});
// Reset the keypad state
setTimeout(() => {
setKeypadState('');
setCodeState(false);
setSubmitButtonEnabled(false);
}, 1000);
} else {
// Play the denied audio
deniedAudio.play();
setCodeValidated(true);
setSubmitButtonEnabled(true);
setTimeout(() => {
setKeypadState('');
setCodeValidated(false);
setSubmitButtonEnabled(false);
}, 1000);
}
}
return (
<div className='h-screen flex justify-center items-center bg-gray-900/40'>
<div className='w-80 p-6 bg-zinc-800 rounded-lg shadow-lg'>
<div className='text-3xl text-white mb-4 text-center'>
Enter Passcode
</div>
<div className='text-2xl text-white mb-4 text-center'>
{codeState
? 'Unlocked'
: keypadState.length === code.length && codeValidated
? 'Code is incorrect'
: keypadState}
</div>
<div className='grid grid-cols-3 gap-2 mb-4'>
{Array.from({ length: 9 }).map((_, i) => (
<button
key={i}
className='w-20 h-20 bg-zinc-700 hover:bg-zinc-700/40 duration-150 text-white text-2xl rounded-lg'
onClick={() => {
if (keypadState.length < code.length) {
setKeypadState((prev) => prev + (i + 1));
playKeypadSound();
}
}}
>
{i + 1}
</button>
))}
<button
className='w-20 h-20 bg-zinc-700 hover:bg-zinc-700/40 duration-150 text-white text-2xl rounded-lg'
onClick={() => setKeypadState((prev) => prev + '0')}
>
0
</button>
<button
className='w-20 h-20 bg-zinc-700 hover:bg-zinc-700/40 duration-150 text-white text-2xl rounded-lg'
onClick={() =>
setKeypadState((prev) => prev.slice(0, -1))
}
>
DEL
</button>
<button
className='w-20 h-20 bg-zinc-700 hover:bg-zinc-700/40 duration-150 text-white text-2xl rounded-lg'
onClick={() => setKeypadState('')}
>
C
</button>
</div>
<button
className='w-full h-12 bg-green-600 hover:bg-green-700/80 duration-150 text-white text-2xl rounded-lg disabled:bg-gray-500 disabled:cursor-not-allowed'
onClick={() => onCodeSubmit(keypadState)}
disabled={
keypadState.length !== code.length ||
submitButtonEnabled
}
>
Submit
</button>
</div>
</div>
</div>
</div>
);
);
};
export default App;

View File

@ -1,18 +1,3 @@
body {
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;
}
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -5,9 +5,9 @@ import App from './components/App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<VisibilityProvider>
<App />
</VisibilityProvider>
</React.StrictMode>,
<React.StrictMode>
<VisibilityProvider>
<App />
</VisibilityProvider>
</React.StrictMode>,
);

View File

@ -1,64 +1,67 @@
import React, {
Context,
createContext,
useContext,
useEffect,
useState,
} from "react";
import { useNuiEvent } from "../hooks/useNuiEvent";
import { fetchNui } from "../utils/fetchNui";
import { isEnvBrowser } from "../utils/misc";
Context,
createContext,
useContext,
useEffect,
useState,
} from 'react';
import { useNuiEvent } from '../hooks/useNuiEvent';
import { fetchNui } from '../utils/fetchNui';
import { isEnvBrowser } from '../utils/misc';
const VisibilityCtx = createContext<VisibilityProviderValue | null>(null);
interface VisibilityProviderValue {
setVisible: (visible: boolean) => void;
visible: boolean;
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: React.ReactNode }> = ({
children,
children,
}) => {
const [visible, setVisible] = useState(false);
const [visible, setVisible] = useState(false);
useNuiEvent<boolean>("setVisible", setVisible);
useNuiEvent<boolean>('setVisible', setVisible);
// Handle pressing escape/backspace
useEffect(() => {
// Only attach listener when we are visible
if (!visible) return;
// Handle pressing escape/backspace
useEffect(() => {
// Only attach listener when we are visible
if (!visible) return;
const keyHandler = (e: KeyboardEvent) => {
if (["Backspace", "Escape"].includes(e.code)) {
if (!isEnvBrowser()) fetchNui("hideFrame");
else setVisible(!visible);
}
};
const keyHandler = (e: KeyboardEvent) => {
if (['Backspace', 'Escape'].includes(e.code)) {
if (!isEnvBrowser()) fetchNui('sticks_keypad:hideFrame');
else setVisible(!visible);
}
};
window.addEventListener("keydown", keyHandler);
window.addEventListener('keydown', keyHandler);
return () => window.removeEventListener("keydown", keyHandler);
}, [visible]);
return () => window.removeEventListener('keydown', keyHandler);
}, [visible]);
return (
<VisibilityCtx.Provider
value={{
visible,
setVisible,
}}
>
<div
style={{ visibility: visible ? "visible" : "hidden", height: "100%" }}
>
{children}
</div>
</VisibilityCtx.Provider>
);
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>,
);
useContext<VisibilityProviderValue>(
VisibilityCtx as Context<VisibilityProviderValue>,
);

View File

@ -32,7 +32,6 @@ export async function fetchNui<T = unknown>(
: "nui-frame-app";
const resp = await fetch(`https://${resourceName}/${eventName}`, options);
const respFormatted = await resp.json();
return respFormatted;

8
web/tailwind.config.js Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};