all done
This commit is contained in:
4259
web/package-lock.json
generated
Normal file
4259
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
6
web/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
BIN
web/src/assets/beep.mp3
Normal file
BIN
web/src/assets/beep.mp3
Normal file
Binary file not shown.
BIN
web/src/assets/denied.mp3
Normal file
BIN
web/src/assets/denied.mp3
Normal file
Binary file not shown.
BIN
web/src/assets/unlock.mp3
Normal file
BIN
web/src/assets/unlock.mp3
Normal file
Binary file not shown.
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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>,
|
||||
);
|
||||
|
@ -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>,
|
||||
);
|
||||
|
@ -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
8
web/tailwind.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
Reference in New Issue
Block a user