mirror of
https://github.com/CommunityOx/ox_lib.git
synced 2026-08-17 06:56:03 +01:00
chore(web): add prettier and format
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
"@vitejs/plugin-react": "^1.3.2",
|
||||
"focus-trap-react": "^9.0.2",
|
||||
"framer-motion": "^6.2.8",
|
||||
"prettier": "^2.7.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-markdown": "^8.0.1",
|
||||
|
||||
3738
web/pnpm-lock.yaml
generated
3738
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
9
web/src/.prettierrc
Normal file
9
web/src/.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"bracketSpacing": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
import Notifications from "./features/notifications/NotificationWrapper";
|
||||
import CircleProgressbar from "./features/progress/CircleProgressbar";
|
||||
import Progressbar from "./features/progress/Progressbar";
|
||||
import TextUI from "./features/textui/TextUI";
|
||||
import InputDialog from "./features/dialog/InputDialog";
|
||||
import ContextMenu from "./features/menu/context/ContextMenu";
|
||||
import Settings from "./features/settings";
|
||||
import { useNuiEvent } from "./hooks/useNuiEvent";
|
||||
import { setClipboard } from "./utils/setClipboard";
|
||||
import { fetchNui } from "./utils/fetchNui";
|
||||
import AlertDialog from "./features/dialog/AlertDialog";
|
||||
import ListMenu from "./features/menu/list";
|
||||
import Dev from "./features/dev";
|
||||
import { isEnvBrowser } from "./utils/misc";
|
||||
import Notifications from './features/notifications/NotificationWrapper';
|
||||
import CircleProgressbar from './features/progress/CircleProgressbar';
|
||||
import Progressbar from './features/progress/Progressbar';
|
||||
import TextUI from './features/textui/TextUI';
|
||||
import InputDialog from './features/dialog/InputDialog';
|
||||
import ContextMenu from './features/menu/context/ContextMenu';
|
||||
import Settings from './features/settings';
|
||||
import { useNuiEvent } from './hooks/useNuiEvent';
|
||||
import { setClipboard } from './utils/setClipboard';
|
||||
import { fetchNui } from './utils/fetchNui';
|
||||
import AlertDialog from './features/dialog/AlertDialog';
|
||||
import ListMenu from './features/menu/list';
|
||||
import Dev from './features/dev';
|
||||
import { isEnvBrowser } from './utils/misc';
|
||||
|
||||
const App: React.FC = () => {
|
||||
useNuiEvent("setClipboard", (data: string) => {
|
||||
useNuiEvent('setClipboard', (data: string) => {
|
||||
setClipboard(data);
|
||||
});
|
||||
|
||||
fetchNui("init");
|
||||
fetchNui('init');
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { debugData } from "../../../utils/debugData";
|
||||
import { AlertProps } from "../../dialog/AlertDialog";
|
||||
import { debugData } from '../../../utils/debugData';
|
||||
import { AlertProps } from '../../dialog/AlertDialog';
|
||||
|
||||
export const debugAlert = () => {
|
||||
debugData<AlertProps>([
|
||||
{
|
||||
action: "sendAlert",
|
||||
action: 'sendAlert',
|
||||
data: {
|
||||
header: "Hello there",
|
||||
content: "General kenobi \n Markdown works",
|
||||
header: 'Hello there',
|
||||
content: 'General kenobi \n Markdown works',
|
||||
centered: true,
|
||||
cancel: true,
|
||||
},
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import { ContextMenuProps } from "../../../interfaces/context";
|
||||
import { debugData } from "../../../utils/debugData";
|
||||
import { ContextMenuProps } from '../../../interfaces/context';
|
||||
import { debugData } from '../../../utils/debugData';
|
||||
|
||||
export const debugContext = () => {
|
||||
debugData<ContextMenuProps>([
|
||||
{
|
||||
action: "showContext",
|
||||
action: 'showContext',
|
||||
data: {
|
||||
title: "Vehicle garage",
|
||||
title: 'Vehicle garage',
|
||||
options: [
|
||||
{ title: "Empty button" },
|
||||
{ title: 'Empty button' },
|
||||
{
|
||||
title: "Example button",
|
||||
description: "Example button description",
|
||||
icon: "inbox",
|
||||
image: "https://i.imgur.com/YAe7k17.jpeg",
|
||||
metadata: [{ label: "Value 1", value: 300 }],
|
||||
title: 'Example button',
|
||||
description: 'Example button description',
|
||||
icon: 'inbox',
|
||||
image: 'https://i.imgur.com/YAe7k17.jpeg',
|
||||
metadata: [{ label: 'Value 1', value: 300 }],
|
||||
},
|
||||
{
|
||||
title: "Menu button",
|
||||
icon: "bars",
|
||||
menu: "other_example_menu",
|
||||
description: "Takes you to another menu",
|
||||
metadata: ["It also has metadata support"],
|
||||
title: 'Menu button',
|
||||
icon: 'bars',
|
||||
menu: 'other_example_menu',
|
||||
description: 'Takes you to another menu',
|
||||
metadata: ['It also has metadata support'],
|
||||
},
|
||||
{
|
||||
title: "Event button",
|
||||
description: "Open a menu and send event data",
|
||||
icon: "check",
|
||||
title: 'Event button',
|
||||
description: 'Open a menu and send event data',
|
||||
icon: 'check',
|
||||
arrow: true,
|
||||
event: "some_event",
|
||||
args: { value1: 300, value2: "Other value" },
|
||||
event: 'some_event',
|
||||
args: { value1: 300, value2: 'Other value' },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import { debugData } from "../../../utils/debugData";
|
||||
import { InputProps } from "../../dialog/InputDialog";
|
||||
import { debugData } from '../../../utils/debugData';
|
||||
import { InputProps } from '../../dialog/InputDialog';
|
||||
|
||||
export const debugInput = () => {
|
||||
debugData<InputProps>([
|
||||
{
|
||||
action: "openDialog",
|
||||
action: 'openDialog',
|
||||
data: {
|
||||
heading: "Police locker",
|
||||
heading: 'Police locker',
|
||||
rows: [
|
||||
{ type: "input", label: "Locker number", placeholder: "420" },
|
||||
{ type: "checkbox", label: "Some checkbox" },
|
||||
{ type: "input", label: "Locker PIN", password: true, icon: "lock" },
|
||||
{ type: "checkbox", label: "Some other checkbox", checked: true },
|
||||
{ type: 'input', label: 'Locker number', placeholder: '420' },
|
||||
{ type: 'checkbox', label: 'Some checkbox' },
|
||||
{ type: 'input', label: 'Locker PIN', password: true, icon: 'lock' },
|
||||
{ type: 'checkbox', label: 'Some other checkbox', checked: true },
|
||||
{
|
||||
type: "select",
|
||||
label: "Locker type",
|
||||
type: 'select',
|
||||
label: 'Locker type',
|
||||
options: [
|
||||
{ value: "option1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
{ value: "option3", label: "Option 3" },
|
||||
{ value: 'option1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
{ value: 'option3', label: 'Option 3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
label: "Number counter",
|
||||
type: 'number',
|
||||
label: 'Number counter',
|
||||
default: 12,
|
||||
min: 3,
|
||||
max: 10,
|
||||
},
|
||||
{ type: "slider", label: "Slide bar", min: 10, max: 50, step: 2 },
|
||||
{ type: 'slider', label: 'Slide bar', min: 10, max: 50, step: 2 },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import { debugData } from "../../../utils/debugData";
|
||||
import { MenuSettings } from "../../menu/list";
|
||||
import { debugData } from '../../../utils/debugData';
|
||||
import { MenuSettings } from '../../menu/list';
|
||||
|
||||
export const debugMenu = () => {
|
||||
debugData<MenuSettings>([
|
||||
{
|
||||
action: "setMenu",
|
||||
action: 'setMenu',
|
||||
data: {
|
||||
// position: "bottom-left",
|
||||
title: "Vehicle garage",
|
||||
title: 'Vehicle garage',
|
||||
items: [
|
||||
{ label: "Option 1", icon: "heart" },
|
||||
{ label: 'Option 1', icon: 'heart' },
|
||||
{
|
||||
label: "Option 2",
|
||||
icon: "basket-shopping",
|
||||
description: "Tooltip description 1",
|
||||
label: 'Option 2',
|
||||
icon: 'basket-shopping',
|
||||
description: 'Tooltip description 1',
|
||||
},
|
||||
{
|
||||
label: "Vehicle class",
|
||||
values: ["Nice", "Super nice", "Extra nice"],
|
||||
icon: "tag",
|
||||
description: "Tooltip description 2",
|
||||
label: 'Vehicle class',
|
||||
values: ['Nice', 'Super nice', 'Extra nice'],
|
||||
icon: 'tag',
|
||||
description: 'Tooltip description 2',
|
||||
},
|
||||
{ label: "Option 1" },
|
||||
{ label: "Option 2" },
|
||||
{ label: 'Option 1' },
|
||||
{ label: 'Option 2' },
|
||||
{
|
||||
label: "Vehicle class",
|
||||
values: ["Nice", "Super nice", "Extra nice"],
|
||||
label: 'Vehicle class',
|
||||
values: ['Nice', 'Super nice', 'Extra nice'],
|
||||
defaultIndex: 1,
|
||||
},
|
||||
{ label: "Option 1" },
|
||||
{ label: "Option 2" },
|
||||
{ label: 'Option 1' },
|
||||
{ label: 'Option 2' },
|
||||
{
|
||||
label: "Vehicle class",
|
||||
values: ["Nice", "Super nice", "Extra nice"],
|
||||
label: 'Vehicle class',
|
||||
values: ['Nice', 'Super nice', 'Extra nice'],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import {
|
||||
CustomNotificationProps,
|
||||
NotificationProps,
|
||||
} from "../../notifications/NotificationWrapper";
|
||||
import { debugData } from "../../../utils/debugData";
|
||||
import { CustomNotificationProps, NotificationProps } from '../../notifications/NotificationWrapper';
|
||||
import { debugData } from '../../../utils/debugData';
|
||||
|
||||
export const debugNotification = () => {
|
||||
debugData<NotificationProps>([
|
||||
{
|
||||
action: "notify",
|
||||
action: 'notify',
|
||||
data: {
|
||||
description: "Dunak is nerd",
|
||||
title: "Dunak",
|
||||
description: 'Dunak is nerd',
|
||||
title: 'Dunak',
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
@@ -20,13 +17,13 @@ export const debugNotification = () => {
|
||||
export const debugCustomNotification = () => {
|
||||
debugData<CustomNotificationProps>([
|
||||
{
|
||||
action: "customNotify",
|
||||
action: 'customNotify',
|
||||
data: {
|
||||
description: "Dunak is nerd",
|
||||
icon: "basket-shopping",
|
||||
description: 'Dunak is nerd',
|
||||
icon: 'basket-shopping',
|
||||
style: {
|
||||
backgroundColor: "#2D3748",
|
||||
color: "white",
|
||||
backgroundColor: '#2D3748',
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { debugData } from "../../../utils/debugData";
|
||||
import { ProgressbarProps } from "../../progress/Progressbar";
|
||||
import { debugData } from '../../../utils/debugData';
|
||||
import { ProgressbarProps } from '../../progress/Progressbar';
|
||||
|
||||
export const debugProgressbar = () => {
|
||||
debugData<ProgressbarProps>([
|
||||
{
|
||||
action: "progress",
|
||||
action: 'progress',
|
||||
data: {
|
||||
label: "Using Lockpick",
|
||||
label: 'Using Lockpick',
|
||||
duration: 8000,
|
||||
},
|
||||
},
|
||||
@@ -16,10 +16,10 @@ export const debugProgressbar = () => {
|
||||
export const debugCircleProgressbar = () => {
|
||||
debugData([
|
||||
{
|
||||
action: "circleProgress",
|
||||
action: 'circleProgress',
|
||||
data: {
|
||||
duration: 8000,
|
||||
label: "Using Armour",
|
||||
label: 'Using Armour',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { debugData } from "../../../utils/debugData";
|
||||
import { debugData } from '../../../utils/debugData';
|
||||
|
||||
export const debugSettings = () => {
|
||||
debugData([
|
||||
{
|
||||
action: "openSettings",
|
||||
data: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
{
|
||||
action: 'openSettings',
|
||||
data: true,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { TextUiProps } from "../../textui/TextUI";
|
||||
import { debugData } from "../../../utils/debugData";
|
||||
import { TextUiProps } from '../../textui/TextUI';
|
||||
import { debugData } from '../../../utils/debugData';
|
||||
|
||||
export const debugTextUI = () => {
|
||||
debugData<TextUiProps>([
|
||||
{
|
||||
action: "textUi",
|
||||
action: 'textUi',
|
||||
data: {
|
||||
text: "[E] - Access locker inventory \n [G] - Do something else",
|
||||
position: "right-center",
|
||||
icon: "door-open",
|
||||
text: '[E] - Access locker inventory \n [G] - Do something else',
|
||||
position: 'right-center',
|
||||
icon: 'door-open',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -10,19 +10,16 @@ import {
|
||||
VStack,
|
||||
Divider,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { debugAlert } from "./debug/alert";
|
||||
import { debugContext } from "./debug/context";
|
||||
import { debugInput } from "./debug/input";
|
||||
import { debugMenu } from "./debug/menu";
|
||||
import {
|
||||
debugCustomNotification,
|
||||
debugNotification,
|
||||
} from "./debug/notification";
|
||||
import { debugCircleProgressbar, debugProgressbar } from "./debug/progress";
|
||||
import { debugTextUI } from "./debug/textui";
|
||||
import { debugSettings } from "./debug/settings";
|
||||
} from '@chakra-ui/react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { debugAlert } from './debug/alert';
|
||||
import { debugContext } from './debug/context';
|
||||
import { debugInput } from './debug/input';
|
||||
import { debugMenu } from './debug/menu';
|
||||
import { debugCustomNotification, debugNotification } from './debug/notification';
|
||||
import { debugCircleProgressbar, debugProgressbar } from './debug/progress';
|
||||
import { debugTextUI } from './debug/textui';
|
||||
import { debugSettings } from './debug/settings';
|
||||
|
||||
const Dev: React.FC = () => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
AlertDialogOverlay,
|
||||
useDisclosure,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import { useRef, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { useNuiEvent } from "../../hooks/useNuiEvent";
|
||||
import { fetchNui } from "../../utils/fetchNui";
|
||||
import { useLocales } from "../../providers/LocaleProvider";
|
||||
} from '@chakra-ui/react';
|
||||
import { useRef, useState } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { useNuiEvent } from '../../hooks/useNuiEvent';
|
||||
import { fetchNui } from '../../utils/fetchNui';
|
||||
import { useLocales } from '../../providers/LocaleProvider';
|
||||
|
||||
export interface AlertProps {
|
||||
header: string;
|
||||
@@ -26,16 +26,16 @@ const AlertDialog: React.FC = () => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const cancelRef = useRef(null);
|
||||
const [dialogData, setDialogData] = useState<AlertProps>({
|
||||
header: "",
|
||||
content: "",
|
||||
header: '',
|
||||
content: '',
|
||||
});
|
||||
|
||||
const closeAlert = (button: string) => {
|
||||
onClose();
|
||||
fetchNui("closeAlert", button);
|
||||
fetchNui('closeAlert', button);
|
||||
};
|
||||
|
||||
useNuiEvent("sendAlert", (data: AlertProps) => {
|
||||
useNuiEvent('sendAlert', (data: AlertProps) => {
|
||||
setDialogData(data);
|
||||
onOpen();
|
||||
});
|
||||
@@ -48,7 +48,7 @@ const AlertDialog: React.FC = () => {
|
||||
isOpen={isOpen}
|
||||
isCentered={dialogData.centered}
|
||||
closeOnOverlayClick={false}
|
||||
onEsc={() => closeAlert("cancel")}
|
||||
onEsc={() => closeAlert('cancel')}
|
||||
>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogContent fontFamily="Inter">
|
||||
@@ -60,14 +60,11 @@ const AlertDialog: React.FC = () => {
|
||||
</AlertDialogBody>
|
||||
<AlertDialogFooter>
|
||||
{dialogData.cancel && (
|
||||
<Button onClick={() => closeAlert("cancel")} mr={3}>
|
||||
<Button onClick={() => closeAlert('cancel')} mr={3}>
|
||||
{locale.ui.cancel}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
colorScheme={dialogData.cancel ? "blue" : undefined}
|
||||
onClick={() => closeAlert("confirm")}
|
||||
>
|
||||
<Button colorScheme={dialogData.cancel ? 'blue' : undefined} onClick={() => closeAlert('confirm')}>
|
||||
{locale.ui.confirm}
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
import { useNuiEvent } from "../../hooks/useNuiEvent";
|
||||
import { useLocales } from "../../providers/LocaleProvider";
|
||||
import { fetchNui } from "../../utils/fetchNui";
|
||||
import {
|
||||
IInput,
|
||||
ICheckbox,
|
||||
ISelect,
|
||||
INumber,
|
||||
ISlider,
|
||||
} from "../../interfaces/dialog";
|
||||
import InputField from "./components/input";
|
||||
import CheckboxField from "./components/checkbox";
|
||||
import SelectField from "./components/select";
|
||||
import NumberField from "./components/number";
|
||||
import SliderField from "./components/slider";
|
||||
import { Modal, ModalOverlay, ModalContent, ModalFooter, ModalHeader, ModalBody, Button } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { useNuiEvent } from '../../hooks/useNuiEvent';
|
||||
import { useLocales } from '../../providers/LocaleProvider';
|
||||
import { fetchNui } from '../../utils/fetchNui';
|
||||
import { IInput, ICheckbox, ISelect, INumber, ISlider } from '../../interfaces/dialog';
|
||||
import InputField from './components/input';
|
||||
import CheckboxField from './components/checkbox';
|
||||
import SelectField from './components/select';
|
||||
import NumberField from './components/number';
|
||||
import SliderField from './components/slider';
|
||||
|
||||
export interface InputProps {
|
||||
heading: string;
|
||||
@@ -31,12 +17,10 @@ export interface InputProps {
|
||||
|
||||
const InputDialog: React.FC = () => {
|
||||
const [fields, setFields] = React.useState<InputProps>({
|
||||
heading: "",
|
||||
rows: [{ type: "input", label: "" }],
|
||||
heading: '',
|
||||
rows: [{ type: 'input', label: '' }],
|
||||
});
|
||||
const [inputData, setInputData] = React.useState<
|
||||
Array<string | number | boolean>
|
||||
>([]);
|
||||
const [inputData, setInputData] = React.useState<Array<string | number | boolean>>([]);
|
||||
const [passwordStates, setPasswordStates] = React.useState<boolean[]>([]);
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const { locale } = useLocales();
|
||||
@@ -48,7 +32,7 @@ const InputDialog: React.FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
useNuiEvent<InputProps>("openDialog", (data) => {
|
||||
useNuiEvent<InputProps>('openDialog', (data) => {
|
||||
setPasswordStates([]);
|
||||
setFields(data);
|
||||
setInputData([]);
|
||||
@@ -57,7 +41,7 @@ const InputDialog: React.FC = () => {
|
||||
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
fetchNui("inputData");
|
||||
fetchNui('inputData');
|
||||
};
|
||||
|
||||
const handleChange = (value: string | number | boolean, index: number) => {
|
||||
@@ -69,74 +53,38 @@ const InputDialog: React.FC = () => {
|
||||
|
||||
const handleConfirm = () => {
|
||||
setVisible(false);
|
||||
fetchNui("inputData", inputData);
|
||||
fetchNui('inputData', inputData);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
isOpen={visible}
|
||||
onClose={handleClose}
|
||||
isCentered
|
||||
closeOnEsc
|
||||
closeOnOverlayClick={false}
|
||||
size="xs"
|
||||
>
|
||||
<Modal isOpen={visible} onClose={handleClose} isCentered closeOnEsc closeOnOverlayClick={false} size="xs">
|
||||
<ModalOverlay />
|
||||
<ModalContent
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && visible) return handleConfirm();
|
||||
if (e.key === 'Enter' && visible) return handleConfirm();
|
||||
}}
|
||||
>
|
||||
<ModalHeader textAlign="center">{fields.heading}</ModalHeader>
|
||||
<ModalBody fontFamily="Poppins" textAlign="left">
|
||||
{fields.rows.map(
|
||||
(
|
||||
row: IInput | ICheckbox | ISelect | INumber | ISlider,
|
||||
index
|
||||
) => (
|
||||
<React.Fragment key={`row-${index}`}>
|
||||
{row.type === "input" && (
|
||||
<InputField
|
||||
key={`input-${index}`}
|
||||
row={row}
|
||||
index={index}
|
||||
handleChange={handleChange}
|
||||
passwordStates={passwordStates}
|
||||
handlePasswordStates={handlePasswordStates}
|
||||
/>
|
||||
)}
|
||||
{row.type === "checkbox" && (
|
||||
<CheckboxField
|
||||
row={row}
|
||||
index={index}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
{row.type === "select" && (
|
||||
<SelectField
|
||||
row={row}
|
||||
index={index}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
{row.type === "number" && (
|
||||
<NumberField
|
||||
row={row}
|
||||
index={index}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
{row.type === "slider" && (
|
||||
<SliderField
|
||||
row={row}
|
||||
index={index}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
{fields.rows.map((row: IInput | ICheckbox | ISelect | INumber | ISlider, index) => (
|
||||
<React.Fragment key={`row-${index}`}>
|
||||
{row.type === 'input' && (
|
||||
<InputField
|
||||
key={`input-${index}`}
|
||||
row={row}
|
||||
index={index}
|
||||
handleChange={handleChange}
|
||||
passwordStates={passwordStates}
|
||||
handlePasswordStates={handlePasswordStates}
|
||||
/>
|
||||
)}
|
||||
{row.type === 'checkbox' && <CheckboxField row={row} index={index} handleChange={handleChange} />}
|
||||
{row.type === 'select' && <SelectField row={row} index={index} handleChange={handleChange} />}
|
||||
{row.type === 'number' && <NumberField row={row} index={index} handleChange={handleChange} />}
|
||||
{row.type === 'slider' && <SliderField row={row} index={index} handleChange={handleChange} />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={3} onClick={handleClose}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Checkbox } from "@chakra-ui/react";
|
||||
import { useEffect } from "react";
|
||||
import { ICheckbox } from "../../../interfaces/dialog";
|
||||
import { Box, Checkbox } from '@chakra-ui/react';
|
||||
import { useEffect } from 'react';
|
||||
import { ICheckbox } from '../../../interfaces/dialog';
|
||||
|
||||
interface Props {
|
||||
row: ICheckbox;
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
|
||||
const CheckboxField: React.FC<Props> = (props) => {
|
||||
useEffect(() => {
|
||||
if(props.row.checked) props.handleChange(props.row.checked, props.index);
|
||||
if (props.row.checked) props.handleChange(props.row.checked, props.index);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import {
|
||||
Box,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
InputRightElement,
|
||||
Text,
|
||||
Input,
|
||||
} from "@chakra-ui/react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useEffect } from "react";
|
||||
import { IInput } from "../../../interfaces/dialog";
|
||||
import { Box, InputGroup, InputLeftElement, InputRightElement, Text, Input } from '@chakra-ui/react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useEffect } from 'react';
|
||||
import { IInput } from '../../../interfaces/dialog';
|
||||
|
||||
interface Props {
|
||||
row: IInput;
|
||||
@@ -20,7 +13,7 @@ interface Props {
|
||||
|
||||
const InputField: React.FC<Props> = (props) => {
|
||||
useEffect(() => {
|
||||
if(props.row.default) props.handleChange(props.row.default, props.index);
|
||||
if (props.row.default) props.handleChange(props.row.default, props.index);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -29,20 +22,13 @@ const InputField: React.FC<Props> = (props) => {
|
||||
<Text>{props.row.label}</Text>
|
||||
<InputGroup>
|
||||
{props.row.icon && (
|
||||
<InputLeftElement
|
||||
pointerEvents="none"
|
||||
children={<FontAwesomeIcon icon={props.row.icon} />}
|
||||
/>
|
||||
<InputLeftElement pointerEvents="none" children={<FontAwesomeIcon icon={props.row.icon} />} />
|
||||
)}
|
||||
<Input
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => props.handleChange(e.target.value, props.index)}
|
||||
placeholder={props.row.placeholder}
|
||||
defaultValue={props.row.default}
|
||||
type={
|
||||
!props.row.password || props.passwordStates[props.index]
|
||||
? "text"
|
||||
: "password"
|
||||
}
|
||||
type={!props.row.password || props.passwordStates[props.index] ? 'text' : 'password'}
|
||||
/>
|
||||
{props.row.password && (
|
||||
<InputRightElement
|
||||
@@ -51,7 +37,7 @@ const InputField: React.FC<Props> = (props) => {
|
||||
children={
|
||||
<FontAwesomeIcon
|
||||
fixedWidth
|
||||
icon={props.passwordStates[props.index] ? "eye" : "eye-slash"}
|
||||
icon={props.passwordStates[props.index] ? 'eye' : 'eye-slash'}
|
||||
fontSize="1em"
|
||||
style={{ paddingRight: 8 }}
|
||||
/>
|
||||
|
||||
@@ -6,9 +6,9 @@ import {
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
} from "@chakra-ui/react";
|
||||
import { useEffect } from "react";
|
||||
import { INumber } from "../../../interfaces/dialog";
|
||||
} from '@chakra-ui/react';
|
||||
import { useEffect } from 'react';
|
||||
import { INumber } from '../../../interfaces/dialog';
|
||||
|
||||
interface Props {
|
||||
row: INumber;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Select } from "@chakra-ui/react";
|
||||
import { useEffect } from "react";
|
||||
import { ISelect } from "../../../interfaces/dialog";
|
||||
import { Box, Select } from '@chakra-ui/react';
|
||||
import { useEffect } from 'react';
|
||||
import { ISelect } from '../../../interfaces/dialog';
|
||||
|
||||
interface Props {
|
||||
row: ISelect;
|
||||
@@ -10,9 +10,9 @@ interface Props {
|
||||
|
||||
const SelectField: React.FC<Props> = (props) => {
|
||||
useEffect(() => {
|
||||
if(props.row.default) {
|
||||
if (props.row.default) {
|
||||
props.row.options?.map((option) => {
|
||||
if(props.row.default === option.value) {
|
||||
if (props.row.default === option.value) {
|
||||
props.handleChange(option.value, props.index);
|
||||
}
|
||||
});
|
||||
@@ -24,12 +24,14 @@ const SelectField: React.FC<Props> = (props) => {
|
||||
<Box mb={3} key={`select-${props.index}`}>
|
||||
<Select
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => props.handleChange(e.target.value, props.index)}
|
||||
defaultValue={props.row.default || ""}
|
||||
defaultValue={props.row.default || ''}
|
||||
>
|
||||
{/* Hacky workaround for selectable placeholder issue */}
|
||||
{!props.row.default && (<option value="" hidden disabled>
|
||||
{props.row.label}
|
||||
</option>)}
|
||||
{!props.row.default && (
|
||||
<option value="" hidden disabled>
|
||||
{props.row.label}
|
||||
</option>
|
||||
)}
|
||||
{props.row.options?.map((option, index) => (
|
||||
<option key={`option-${index}`} value={option.value}>
|
||||
{option.label || option.value}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Text, Slider, SliderTrack, SliderFilledTrack, SliderThumb, HStack, Tooltip } from "@chakra-ui/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ISlider } from "../../../interfaces/dialog";
|
||||
import { Box, Text, Slider, SliderTrack, SliderFilledTrack, SliderThumb, HStack, Tooltip } from '@chakra-ui/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ISlider } from '../../../interfaces/dialog';
|
||||
|
||||
interface Props {
|
||||
row: ISlider;
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
|
||||
const SliderField: React.FC<Props> = (props) => {
|
||||
useEffect(() => {
|
||||
if(props.row.default) props.handleChange(props.row.default, props.index);
|
||||
if (props.row.default) props.handleChange(props.row.default, props.index);
|
||||
}, []);
|
||||
|
||||
const [sliderValue, setSliderValue] = useState(props.row.default || props.row.min || 0);
|
||||
@@ -30,12 +30,7 @@ const SliderField: React.FC<Props> = (props) => {
|
||||
<SliderTrack>
|
||||
<SliderFilledTrack />
|
||||
</SliderTrack>
|
||||
<Tooltip
|
||||
hasArrow
|
||||
label={sliderValue}
|
||||
placement="bottom"
|
||||
gutter={10}
|
||||
>
|
||||
<Tooltip hasArrow label={sliderValue} placement="bottom" gutter={10}>
|
||||
<SliderThumb />
|
||||
</Tooltip>
|
||||
</Slider>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { useNuiEvent } from "../../../hooks/useNuiEvent";
|
||||
import { Box, Text, Flex, ScaleFade } from "@chakra-ui/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ContextMenuProps } from "../../../interfaces/context";
|
||||
import Item from "./Item";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { fetchNui } from "../../../utils/fetchNui";
|
||||
import { useNuiEvent } from '../../../hooks/useNuiEvent';
|
||||
import { Box, Text, Flex, ScaleFade } from '@chakra-ui/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ContextMenuProps } from '../../../interfaces/context';
|
||||
import Item from './Item';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { fetchNui } from '../../../utils/fetchNui';
|
||||
|
||||
const openMenu = (id: string | undefined) => {
|
||||
fetchNui<ContextMenuProps>("openContext", id);
|
||||
fetchNui<ContextMenuProps>('openContext', id);
|
||||
};
|
||||
|
||||
const ContextMenu: React.FC = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [contextMenu, setContextMenu] = useState<ContextMenuProps>({
|
||||
title: "",
|
||||
options: { "": { description: "", metadata: [] } },
|
||||
title: '',
|
||||
options: { '': { description: '', metadata: [] } },
|
||||
});
|
||||
|
||||
const closeContext = () => {
|
||||
setVisible(false);
|
||||
fetchNui("closeContext");
|
||||
fetchNui('closeContext');
|
||||
};
|
||||
|
||||
// Hides the context menu on ESC
|
||||
@@ -27,17 +27,17 @@ const ContextMenu: React.FC = () => {
|
||||
if (!visible) return;
|
||||
|
||||
const keyHandler = (e: KeyboardEvent) => {
|
||||
if (["Escape"].includes(e.code)) closeContext();
|
||||
if (['Escape'].includes(e.code)) closeContext();
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", keyHandler);
|
||||
window.addEventListener('keydown', keyHandler);
|
||||
|
||||
return () => window.removeEventListener("keydown", keyHandler);
|
||||
return () => window.removeEventListener('keydown', keyHandler);
|
||||
}, [visible]);
|
||||
|
||||
useNuiEvent("hideContext", () => setVisible(false));
|
||||
useNuiEvent('hideContext', () => setVisible(false));
|
||||
|
||||
useNuiEvent<ContextMenuProps>("showContext", async (data) => {
|
||||
useNuiEvent<ContextMenuProps>('showContext', async (data) => {
|
||||
if (visible) {
|
||||
setVisible(false);
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
@@ -47,13 +47,7 @@ const ContextMenu: React.FC = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex
|
||||
position="absolute"
|
||||
w="75%"
|
||||
h="80%"
|
||||
justifyContent="flex-end"
|
||||
alignItems="center"
|
||||
>
|
||||
<Flex position="absolute" w="75%" h="80%" justifyContent="flex-end" alignItems="center">
|
||||
<ScaleFade in={visible} unmountOnExit>
|
||||
<Box w="xs" h={580}>
|
||||
<Flex justifyContent="center" alignItems="center" mb={3}>
|
||||
@@ -68,7 +62,7 @@ const ContextMenu: React.FC = () => {
|
||||
alignItems="center"
|
||||
marginRight={2}
|
||||
p={2}
|
||||
_hover={{ bg: "gray.700" }}
|
||||
_hover={{ bg: 'gray.700' }}
|
||||
transition="300ms"
|
||||
onClick={() => openMenu(contextMenu.menu)}
|
||||
>
|
||||
@@ -76,13 +70,7 @@ const ContextMenu: React.FC = () => {
|
||||
</Flex>
|
||||
)}
|
||||
<Box borderRadius="md" bg="gray.800" flex="1 85%">
|
||||
<Text
|
||||
fontFamily="Poppins"
|
||||
fontSize="md"
|
||||
p={2}
|
||||
textAlign="center"
|
||||
fontWeight="light"
|
||||
>
|
||||
<Text fontFamily="Poppins" fontSize="md" p={2} textAlign="center" fontWeight="light">
|
||||
{contextMenu.title}
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -96,7 +84,7 @@ const ContextMenu: React.FC = () => {
|
||||
alignItems="center"
|
||||
marginLeft={2}
|
||||
p={2}
|
||||
_hover={{ bg: "gray.700" }}
|
||||
_hover={{ bg: 'gray.700' }}
|
||||
transition="300ms"
|
||||
onClick={() => closeContext()}
|
||||
>
|
||||
|
||||
@@ -10,17 +10,17 @@ import {
|
||||
Spacer,
|
||||
Image,
|
||||
HStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Option, ContextMenuProps } from "../../../interfaces/context";
|
||||
import { fetchNui } from "../../../utils/fetchNui";
|
||||
} from '@chakra-ui/react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Option, ContextMenuProps } from '../../../interfaces/context';
|
||||
import { fetchNui } from '../../../utils/fetchNui';
|
||||
|
||||
const openMenu = (id: string | undefined) => {
|
||||
fetchNui<ContextMenuProps>("openContext", id);
|
||||
fetchNui<ContextMenuProps>('openContext', id);
|
||||
};
|
||||
|
||||
const clickContext = (id: string) => {
|
||||
fetchNui("clickContext", id);
|
||||
fetchNui('clickContext', id);
|
||||
};
|
||||
|
||||
const Item: React.FC<{
|
||||
@@ -28,12 +28,7 @@ const Item: React.FC<{
|
||||
}> = ({ option }) => {
|
||||
return (
|
||||
<>
|
||||
<Popover
|
||||
placement="right-start"
|
||||
trigger="hover"
|
||||
eventListeners={{ scroll: true }}
|
||||
isLazy
|
||||
>
|
||||
<Popover placement="right-start" trigger="hover" eventListeners={{ scroll: true }} isLazy>
|
||||
<PopoverTrigger>
|
||||
<Box
|
||||
bg="gray.800"
|
||||
@@ -45,16 +40,12 @@ const Item: React.FC<{
|
||||
fontFamily="Poppins"
|
||||
fontSize="md"
|
||||
transition="300ms"
|
||||
_hover={{ bg: "gray.700" }}
|
||||
_hover={{ bg: 'gray.700' }}
|
||||
>
|
||||
<Flex
|
||||
w="100%"
|
||||
alignItems="center"
|
||||
onClick={() =>
|
||||
option[1].menu
|
||||
? openMenu(option[1].menu)
|
||||
: clickContext(option[0])
|
||||
}
|
||||
onClick={() => (option[1].menu ? openMenu(option[1].menu) : clickContext(option[0]))}
|
||||
>
|
||||
{option[1]?.icon && (
|
||||
<FontAwesomeIcon
|
||||
@@ -63,7 +54,7 @@ const Item: React.FC<{
|
||||
fontSize={20}
|
||||
style={{
|
||||
marginRight: 10,
|
||||
justifySelf: "center",
|
||||
justifySelf: 'center',
|
||||
color: option[1].iconColor,
|
||||
}}
|
||||
/>
|
||||
@@ -83,12 +74,7 @@ const Item: React.FC<{
|
||||
{(option[1].menu || option[1].arrow) && (
|
||||
<>
|
||||
<Spacer />
|
||||
<Box
|
||||
alignSelf="center"
|
||||
justifySelf="center"
|
||||
mr={4}
|
||||
fontSize="xl"
|
||||
>
|
||||
<Box alignSelf="center" justifySelf="center" mr={4} fontSize="xl">
|
||||
<FontAwesomeIcon icon="chevron-right" />
|
||||
</Box>
|
||||
</>
|
||||
@@ -108,28 +94,19 @@ const Item: React.FC<{
|
||||
<>
|
||||
{option[1].image && <Image src={option[1].image} />}
|
||||
{Array.isArray(option[1].metadata) ? (
|
||||
option[1].metadata.map(
|
||||
(
|
||||
metadata: string | { label: string; value: any },
|
||||
index: number
|
||||
) => (
|
||||
<Text key={`context-metadata-${index}`}>
|
||||
{typeof metadata === "string"
|
||||
? `${metadata}`
|
||||
: `${metadata.label}: ${metadata.value}`}
|
||||
</Text>
|
||||
)
|
||||
)
|
||||
option[1].metadata.map((metadata: string | { label: string; value: any }, index: number) => (
|
||||
<Text key={`context-metadata-${index}`}>
|
||||
{typeof metadata === 'string' ? `${metadata}` : `${metadata.label}: ${metadata.value}`}
|
||||
</Text>
|
||||
))
|
||||
) : (
|
||||
<>
|
||||
{typeof option[1].metadata === "object" &&
|
||||
Object.entries(option[1].metadata).map(
|
||||
(metadata: { [key: string]: any }, index) => (
|
||||
<Text key={`context-metadata-${index}`}>
|
||||
{metadata[0]}: {metadata[1]}
|
||||
</Text>
|
||||
)
|
||||
)}
|
||||
{typeof option[1].metadata === 'object' &&
|
||||
Object.entries(option[1].metadata).map((metadata: { [key: string]: any }, index) => (
|
||||
<Text key={`context-metadata-${index}`}>
|
||||
{metadata[0]}: {metadata[1]}
|
||||
</Text>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
import { Box, Text } from '@chakra-ui/react';
|
||||
|
||||
const Header: React.FC<{ title: string }> = ({ title }) => {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Box, Flex, Stack, Spacer, Text, IconProps } from "@chakra-ui/react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { forwardRef } from "react";
|
||||
import type { MenuItem } from "./index";
|
||||
import { Box, Flex, Stack, Spacer, Text, IconProps } from '@chakra-ui/react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { forwardRef } from 'react';
|
||||
import type { MenuItem } from './index';
|
||||
|
||||
interface Props {
|
||||
item: MenuItem;
|
||||
@@ -19,7 +19,7 @@ const ListItem = forwardRef<Array<HTMLDivElement | null>, Props>(({ item, index,
|
||||
p={2}
|
||||
height="60px"
|
||||
key={`item-${index}`}
|
||||
_focus={{ bg: "#373A40", outline: "none" }}
|
||||
_focus={{ bg: '#373A40', outline: 'none' }}
|
||||
ref={(element) => {
|
||||
if (ref)
|
||||
// @ts-ignore i cba
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Box, Stack, Tooltip } from "@chakra-ui/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useNuiEvent } from "../../../hooks/useNuiEvent";
|
||||
import ListItem from "./ListItem";
|
||||
import Header from "./Header";
|
||||
import FocusTrap from "focus-trap-react";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { fetchNui } from "../../../utils/fetchNui";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React from "react";
|
||||
import { Box, Stack, Tooltip } from '@chakra-ui/react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useNuiEvent } from '../../../hooks/useNuiEvent';
|
||||
import ListItem from './ListItem';
|
||||
import Header from './Header';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
import { fetchNui } from '../../../utils/fetchNui';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React from 'react';
|
||||
|
||||
export interface MenuItem {
|
||||
label: string;
|
||||
@@ -19,15 +19,15 @@ export interface MenuItem {
|
||||
}
|
||||
|
||||
export interface MenuSettings {
|
||||
position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
||||
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||
title: string;
|
||||
items: Array<MenuItem>;
|
||||
}
|
||||
|
||||
const ListMenu: React.FC = () => {
|
||||
const [menu, setMenu] = useState<MenuSettings>({
|
||||
position: "top-left",
|
||||
title: "",
|
||||
position: 'top-left',
|
||||
title: '',
|
||||
items: [],
|
||||
});
|
||||
const [selected, setSelected] = useState(0);
|
||||
@@ -37,53 +37,47 @@ const ListMenu: React.FC = () => {
|
||||
|
||||
const closeMenu = (ignoreFetch?: boolean) => {
|
||||
setVisible(false);
|
||||
if (!ignoreFetch) fetchNui("closeMenu");
|
||||
if (!ignoreFetch) fetchNui('closeMenu');
|
||||
};
|
||||
|
||||
const moveMenu = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
switch (e.code) {
|
||||
case "ArrowDown":
|
||||
case 'ArrowDown':
|
||||
setSelected((selected) => {
|
||||
if (selected >= menu.items.length - 1) return (selected = 0);
|
||||
return selected + 1;
|
||||
});
|
||||
break;
|
||||
case "ArrowUp":
|
||||
case 'ArrowUp':
|
||||
setSelected((selected) => {
|
||||
if (selected <= 0) return (selected = menu.items.length - 1);
|
||||
return selected - 1;
|
||||
});
|
||||
break;
|
||||
case "ArrowRight":
|
||||
case 'ArrowRight':
|
||||
if (!Array.isArray(menu.items[selected].values)) return;
|
||||
setIndexStates({
|
||||
...indexStates,
|
||||
[selected]:
|
||||
indexStates[selected] + 1 <=
|
||||
menu.items[selected].values?.length! - 1
|
||||
indexStates[selected] + 1 <= menu.items[selected].values?.length! - 1
|
||||
? indexStates[selected] + 1
|
||||
: (indexStates[selected] = 0),
|
||||
});
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
case 'ArrowLeft':
|
||||
if (!Array.isArray(menu.items[selected].values)) return;
|
||||
setIndexStates({
|
||||
...indexStates,
|
||||
[selected]:
|
||||
indexStates[selected] - 1 >= 0
|
||||
? indexStates[selected] - 1
|
||||
: (indexStates[selected] =
|
||||
menu.items[selected].values?.length! - 1),
|
||||
: (indexStates[selected] = menu.items[selected].values?.length! - 1),
|
||||
});
|
||||
break;
|
||||
case "Enter":
|
||||
case 'Enter':
|
||||
if (!menu.items[selected]) return;
|
||||
fetchNui("confirmSelected", [selected, indexStates[selected]]).catch();
|
||||
if (
|
||||
menu.items[selected].close === undefined ||
|
||||
menu.items[selected].close
|
||||
)
|
||||
setVisible(false);
|
||||
fetchNui('confirmSelected', [selected, indexStates[selected]]).catch();
|
||||
if (menu.items[selected].close === undefined || menu.items[selected].close) setVisible(false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -91,7 +85,7 @@ const ListMenu: React.FC = () => {
|
||||
useEffect(() => {
|
||||
if (!menu.items[selected]?.values) return;
|
||||
const timer = setTimeout(() => {
|
||||
fetchNui("changeIndex", [selected, indexStates[selected]]).catch();
|
||||
fetchNui('changeIndex', [selected, indexStates[selected]]).catch();
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}, [indexStates]);
|
||||
@@ -99,13 +93,13 @@ const ListMenu: React.FC = () => {
|
||||
useEffect(() => {
|
||||
if (!menu.items[selected]) return;
|
||||
listRefs.current[selected]?.scrollIntoView({
|
||||
block: "nearest",
|
||||
inline: "start",
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
});
|
||||
listRefs.current[selected]?.focus({ preventScroll: true });
|
||||
// debounces the callback to avoid spam
|
||||
const timer = setTimeout(() => {
|
||||
fetchNui("changeSelected", [selected, indexStates[selected]]).catch();
|
||||
fetchNui('changeSelected', [selected, indexStates[selected]]).catch();
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}, [selected, menu]);
|
||||
@@ -114,26 +108,25 @@ const ListMenu: React.FC = () => {
|
||||
if (!visible) return;
|
||||
|
||||
const keyHandler = (e: KeyboardEvent) => {
|
||||
if (["Escape", "Backspace"].includes(e.code)) closeMenu();
|
||||
if (['Escape', 'Backspace'].includes(e.code)) closeMenu();
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", keyHandler);
|
||||
window.addEventListener('keydown', keyHandler);
|
||||
|
||||
return () => window.removeEventListener("keydown", keyHandler);
|
||||
return () => window.removeEventListener('keydown', keyHandler);
|
||||
}, [visible]);
|
||||
|
||||
useNuiEvent("closeMenu", () => closeMenu(true));
|
||||
useNuiEvent('closeMenu', () => closeMenu(true));
|
||||
|
||||
useNuiEvent("setMenu", (data: MenuSettings) => {
|
||||
useNuiEvent('setMenu', (data: MenuSettings) => {
|
||||
setSelected(0);
|
||||
if (!data.position) data.position = "top-left";
|
||||
if (!data.position) data.position = 'top-left';
|
||||
listRefs.current = [];
|
||||
setMenu(data);
|
||||
setVisible(true);
|
||||
let arrayIndexes: { [key: number]: number } = {};
|
||||
for (let i = 0; i < data.items.length; i++) {
|
||||
if (Array.isArray(data.items[i].values))
|
||||
arrayIndexes[i] = (data.items[i].defaultIndex || 1) - 1;
|
||||
if (Array.isArray(data.items[i].values)) arrayIndexes[i] = (data.items[i].defaultIndex || 1) - 1;
|
||||
}
|
||||
setIndexStates(arrayIndexes);
|
||||
listRefs.current[0]?.focus();
|
||||
@@ -154,39 +147,13 @@ const ListMenu: React.FC = () => {
|
||||
<Box
|
||||
position="absolute"
|
||||
pointerEvents="none"
|
||||
mt={
|
||||
menu.position === "top-left" || menu.position === "top-right"
|
||||
? 5
|
||||
: 0
|
||||
}
|
||||
ml={
|
||||
menu.position === "top-left" || menu.position === "bottom-left"
|
||||
? 5
|
||||
: 0
|
||||
}
|
||||
mr={
|
||||
menu.position === "top-right" || menu.position === "bottom-right"
|
||||
? 5
|
||||
: 0
|
||||
}
|
||||
mb={
|
||||
menu.position === "bottom-left" ||
|
||||
menu.position === "bottom-right"
|
||||
? 5
|
||||
: 0
|
||||
}
|
||||
right={
|
||||
menu.position === "top-right" || menu.position === "bottom-right"
|
||||
? 1
|
||||
: undefined
|
||||
}
|
||||
left={menu.position === "bottom-left" ? 1 : undefined}
|
||||
bottom={
|
||||
menu.position === "bottom-left" ||
|
||||
menu.position === "bottom-right"
|
||||
? 1
|
||||
: undefined
|
||||
}
|
||||
mt={menu.position === 'top-left' || menu.position === 'top-right' ? 5 : 0}
|
||||
ml={menu.position === 'top-left' || menu.position === 'bottom-left' ? 5 : 0}
|
||||
mr={menu.position === 'top-right' || menu.position === 'bottom-right' ? 5 : 0}
|
||||
mb={menu.position === 'bottom-left' || menu.position === 'bottom-right' ? 5 : 0}
|
||||
right={menu.position === 'top-right' || menu.position === 'bottom-right' ? 1 : undefined}
|
||||
left={menu.position === 'bottom-left' ? 1 : undefined}
|
||||
bottom={menu.position === 'bottom-left' || menu.position === 'bottom-right' ? 1 : undefined}
|
||||
>
|
||||
<Header title={menu.title} />
|
||||
<Box
|
||||
@@ -194,11 +161,7 @@ const ListMenu: React.FC = () => {
|
||||
height="fit-content"
|
||||
maxHeight={415}
|
||||
overflow="hidden"
|
||||
borderRadius={
|
||||
menu.items.length <= 6 || selected === menu.items.length - 1
|
||||
? "md"
|
||||
: undefined
|
||||
}
|
||||
borderRadius={menu.items.length <= 6 || selected === menu.items.length - 1 ? 'md' : undefined}
|
||||
bg="#141517"
|
||||
fontFamily="Nunito"
|
||||
borderTopLeftRadius="none"
|
||||
@@ -210,12 +173,7 @@ const ListMenu: React.FC = () => {
|
||||
{menu.items.map((item, index) => (
|
||||
<React.Fragment key={`menu-item-${index}`}>
|
||||
{item.label && (
|
||||
<ListItem
|
||||
index={index}
|
||||
item={item}
|
||||
scrollIndex={indexStates[index]}
|
||||
ref={listRefs}
|
||||
/>
|
||||
<ListItem index={index} item={item} scrollIndex={indexStates[index]} ref={listRefs} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
@@ -223,18 +181,8 @@ const ListMenu: React.FC = () => {
|
||||
</FocusTrap>
|
||||
</Box>
|
||||
{menu.items.length > 6 && selected !== menu.items.length - 1 && (
|
||||
<Box
|
||||
bg="#141517"
|
||||
textAlign="center"
|
||||
borderBottomLeftRadius="md"
|
||||
borderBottomRightRadius="md"
|
||||
height={25}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon="chevron-down"
|
||||
color="#909296"
|
||||
fontSize={20}
|
||||
/>
|
||||
<Box bg="#141517" textAlign="center" borderBottomLeftRadius="md" borderBottomRightRadius="md" height={25}>
|
||||
<FontAwesomeIcon icon="chevron-down" color="#909296" fontSize={20} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
import {
|
||||
useToast,
|
||||
type ToastPositionWithLogical,
|
||||
Box,
|
||||
HStack,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { useNuiEvent } from "../../hooks/useNuiEvent";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useToast, type ToastPositionWithLogical, Box, HStack, Text } from '@chakra-ui/react';
|
||||
import { useNuiEvent } from '../../hooks/useNuiEvent';
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
export interface NotificationProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
position?: ToastPositionWithLogical;
|
||||
status?: "info" | "warning" | "success" | "error";
|
||||
status?: 'info' | 'warning' | 'success' | 'error';
|
||||
id?: number;
|
||||
}
|
||||
|
||||
@@ -33,31 +27,20 @@ export interface CustomNotificationProps {
|
||||
const Notifications: React.FC = () => {
|
||||
const toast = useToast();
|
||||
|
||||
useNuiEvent<CustomNotificationProps>("customNotify", (data) => {
|
||||
useNuiEvent<CustomNotificationProps>('customNotify', (data) => {
|
||||
if (!data.title && !data.description) return;
|
||||
if (data.id && toast.isActive(data.id)) return;
|
||||
if (!data.icon) {
|
||||
data.icon =
|
||||
data.type === "error"
|
||||
? "circle-xmark"
|
||||
: data.type === "success"
|
||||
? "circle-check"
|
||||
: "circle-info";
|
||||
data.icon = data.type === 'error' ? 'circle-xmark' : data.type === 'success' ? 'circle-check' : 'circle-info';
|
||||
}
|
||||
|
||||
const id = data.id;
|
||||
toast({
|
||||
id,
|
||||
duration: data.duration || 3000,
|
||||
position: data.position || "top-right",
|
||||
position: data.position || 'top-right',
|
||||
render: () => (
|
||||
<Box
|
||||
className={`toast-${data.type || "inform"}`}
|
||||
style={data.style}
|
||||
p={2}
|
||||
borderRadius="sm"
|
||||
boxShadow="md"
|
||||
>
|
||||
<Box className={`toast-${data.type || 'inform'}`} style={data.style} p={2} borderRadius="sm" boxShadow="md">
|
||||
<HStack spacing={0}>
|
||||
{data.icon && (
|
||||
<FontAwesomeIcon
|
||||
@@ -78,7 +61,7 @@ const Notifications: React.FC = () => {
|
||||
});
|
||||
});
|
||||
|
||||
useNuiEvent<NotificationProps>("notify", (data) => {
|
||||
useNuiEvent<NotificationProps>('notify', (data) => {
|
||||
if (!data.title && !data.description) return;
|
||||
if (data.id && toast.isActive(data.id)) return;
|
||||
const id = data.id;
|
||||
@@ -87,7 +70,7 @@ const Notifications: React.FC = () => {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
duration: data.duration || 4000,
|
||||
position: data.position || "top-right",
|
||||
position: data.position || 'top-right',
|
||||
status: data.status,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
import React from "react";
|
||||
import {
|
||||
CircularProgress,
|
||||
CircularProgressLabel,
|
||||
Flex,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { useNuiEvent } from "../../hooks/useNuiEvent";
|
||||
import { fetchNui } from "../../utils/fetchNui";
|
||||
import React from 'react';
|
||||
import { CircularProgress, CircularProgressLabel, Flex, Text } from '@chakra-ui/react';
|
||||
import { useNuiEvent } from '../../hooks/useNuiEvent';
|
||||
import { fetchNui } from '../../utils/fetchNui';
|
||||
|
||||
export interface CircleProgressbarProps {
|
||||
label?: string;
|
||||
duration: number;
|
||||
position?: "middle" | "bottom";
|
||||
position?: 'middle' | 'bottom';
|
||||
percent?: boolean;
|
||||
}
|
||||
|
||||
const CircleProgressbar: React.FC = () => {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const [progressDuration, setProgressDuration] = React.useState(0);
|
||||
const [position, setPosition] = React.useState<"middle" | "bottom">("middle");
|
||||
const [position, setPosition] = React.useState<'middle' | 'bottom'>('middle');
|
||||
const [value, setValue] = React.useState(0);
|
||||
const [label, setLabel] = React.useState("");
|
||||
const [label, setLabel] = React.useState('');
|
||||
const [cancelled, setCancelled] = React.useState(false);
|
||||
|
||||
const progressComplete = () => {
|
||||
setVisible(false);
|
||||
fetchNui("progressComplete");
|
||||
fetchNui('progressComplete');
|
||||
};
|
||||
|
||||
const progressCancel = () => {
|
||||
@@ -34,16 +29,16 @@ const CircleProgressbar: React.FC = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
useNuiEvent("progressCancel", progressCancel);
|
||||
useNuiEvent('progressCancel', progressCancel);
|
||||
|
||||
useNuiEvent<CircleProgressbarProps>("circleProgress", (data) => {
|
||||
useNuiEvent<CircleProgressbarProps>('circleProgress', (data) => {
|
||||
if (visible) return;
|
||||
setCancelled(false);
|
||||
setVisible(true);
|
||||
setValue(0);
|
||||
setLabel(data.label || "");
|
||||
setLabel(data.label || '');
|
||||
setProgressDuration(data.duration);
|
||||
setPosition(data.position || "middle");
|
||||
setPosition(data.position || 'middle');
|
||||
const onePercent = data.duration * 0.01;
|
||||
const updateProgress = setInterval(() => {
|
||||
setValue((previousValue) => {
|
||||
@@ -56,7 +51,7 @@ const CircleProgressbar: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Flex
|
||||
h={position === "middle" ? "100%" : "20%"}
|
||||
h={position === 'middle' ? '100%' : '20%'}
|
||||
w="100%"
|
||||
position="absolute"
|
||||
bottom="0"
|
||||
@@ -71,29 +66,27 @@ const CircleProgressbar: React.FC = () => {
|
||||
trackColor="rgba(0, 0, 0, 0.6)"
|
||||
onAnimationEnd={progressComplete}
|
||||
thickness={6}
|
||||
color={cancelled ? "rgb(198, 40, 40)" : "white"}
|
||||
color={cancelled ? 'rgb(198, 40, 40)' : 'white'}
|
||||
sx={
|
||||
!cancelled
|
||||
? {
|
||||
".chakra-progress__indicator": {
|
||||
transition: "none !important",
|
||||
animation: "progress linear forwards !important",
|
||||
'.chakra-progress__indicator': {
|
||||
transition: 'none !important',
|
||||
animation: 'progress linear forwards !important',
|
||||
animationDuration: `${progressDuration}ms !important`,
|
||||
opacity: "1 !important",
|
||||
opacity: '1 !important',
|
||||
},
|
||||
}
|
||||
: {
|
||||
// Currently unused
|
||||
".chakra-progress__indicator": {
|
||||
transition: "none !important",
|
||||
strokeDasharray: "264, 0 !important", // sets circle to full
|
||||
'.chakra-progress__indicator': {
|
||||
transition: 'none !important',
|
||||
strokeDasharray: '264, 0 !important', // sets circle to full
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<CircularProgressLabel fontFamily="Fira Mono">
|
||||
{value}%
|
||||
</CircularProgressLabel>
|
||||
<CircularProgressLabel fontFamily="Fira Mono">{value}%</CircularProgressLabel>
|
||||
</CircularProgress>
|
||||
<Text fontFamily="Inter" isTruncated fontSize={18} fontWeight="light">
|
||||
{label}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { Text, Flex, Box } from "@chakra-ui/react";
|
||||
import { useNuiEvent } from "../../hooks/useNuiEvent";
|
||||
import { fetchNui } from "../../utils/fetchNui";
|
||||
import React from 'react';
|
||||
import { Text, Flex, Box } from '@chakra-ui/react';
|
||||
import { useNuiEvent } from '../../hooks/useNuiEvent';
|
||||
import { fetchNui } from '../../utils/fetchNui';
|
||||
|
||||
export interface ProgressbarProps {
|
||||
label: string;
|
||||
@@ -10,13 +10,13 @@ export interface ProgressbarProps {
|
||||
|
||||
const Progressbar: React.FC = () => {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const [label, setLabel] = React.useState("");
|
||||
const [label, setLabel] = React.useState('');
|
||||
const [duration, setDuration] = React.useState(0);
|
||||
const [cancelled, setCancelled] = React.useState(false);
|
||||
|
||||
const progressComplete = () => {
|
||||
setVisible(false);
|
||||
fetchNui("progressComplete");
|
||||
fetchNui('progressComplete');
|
||||
};
|
||||
|
||||
const progressCancel = () => {
|
||||
@@ -24,9 +24,9 @@ const Progressbar: React.FC = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
useNuiEvent("progressCancel", progressCancel);
|
||||
useNuiEvent('progressCancel', progressCancel);
|
||||
|
||||
useNuiEvent<ProgressbarProps>("progress", (data) => {
|
||||
useNuiEvent<ProgressbarProps>('progress', (data) => {
|
||||
setCancelled(false);
|
||||
setVisible(true);
|
||||
setLabel(data.label);
|
||||
@@ -34,14 +34,7 @@ const Progressbar: React.FC = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex
|
||||
h="30%"
|
||||
w="100%"
|
||||
position="absolute"
|
||||
bottom="0"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Flex h="30%" w="100%" position="absolute" bottom="0" justifyContent="center" alignItems="center">
|
||||
<Box width={350}>
|
||||
{visible && (
|
||||
<Box
|
||||
@@ -58,16 +51,16 @@ const Progressbar: React.FC = () => {
|
||||
sx={
|
||||
!cancelled
|
||||
? {
|
||||
width: "0%",
|
||||
backgroundColor: "green.400",
|
||||
animation: "progress-bar linear",
|
||||
width: '0%',
|
||||
backgroundColor: 'green.400',
|
||||
animation: 'progress-bar linear',
|
||||
animationDuration: `${duration}ms`,
|
||||
}
|
||||
: {
|
||||
// Currently unused
|
||||
width: "100%",
|
||||
animationPlayState: "paused",
|
||||
backgroundColor: "rgb(198, 40, 40)",
|
||||
width: '100%',
|
||||
animationPlayState: 'paused',
|
||||
backgroundColor: 'rgb(198, 40, 40)',
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HStack, Text, Select, Spacer } from "@chakra-ui/react";
|
||||
import { useLocales } from "../../../providers/LocaleProvider";
|
||||
import { fetchNui } from "../../../utils/fetchNui";
|
||||
import { HStack, Text, Select, Spacer } from '@chakra-ui/react';
|
||||
import { useLocales } from '../../../providers/LocaleProvider';
|
||||
import { fetchNui } from '../../../utils/fetchNui';
|
||||
|
||||
const LocaleSetting: React.FC<{
|
||||
languages: string[];
|
||||
@@ -11,18 +11,14 @@ const LocaleSetting: React.FC<{
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
props.setSelectValue(e.target.value);
|
||||
fetchNui("getLocale", e.target.value);
|
||||
fetchNui('getLocale', e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<HStack spacing={5}>
|
||||
<Text>{locale.ui.settings.language}</Text>
|
||||
<Spacer />
|
||||
<Select
|
||||
onChange={(e) => handleChange(e)}
|
||||
value={props.selectValue}
|
||||
w={150}
|
||||
>
|
||||
<Select onChange={(e) => handleChange(e)} value={props.selectValue} w={150}>
|
||||
{props.languages.map((language) => (
|
||||
<option key={language} value={language}>
|
||||
{language}
|
||||
|
||||
@@ -7,26 +7,26 @@ import {
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import LocaleSetting from "./components/locale";
|
||||
import { useState } from "react";
|
||||
import { useNuiEvent } from "../../hooks/useNuiEvent";
|
||||
import { fetchNui } from "../../utils/fetchNui";
|
||||
import { useLocales } from "../../providers/LocaleProvider";
|
||||
} from '@chakra-ui/react';
|
||||
import LocaleSetting from './components/locale';
|
||||
import { useState } from 'react';
|
||||
import { useNuiEvent } from '../../hooks/useNuiEvent';
|
||||
import { fetchNui } from '../../utils/fetchNui';
|
||||
import { useLocales } from '../../providers/LocaleProvider';
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { locale } = useLocales();
|
||||
const [languages, setLanguages] = useState([""]);
|
||||
const [selectValue, setSelectValue] = useState("");
|
||||
const [languages, setLanguages] = useState(['']);
|
||||
const [selectValue, setSelectValue] = useState('');
|
||||
|
||||
const closeSettings = () => {
|
||||
onClose();
|
||||
fetchNui("closeSettings");
|
||||
fetchNui('closeSettings');
|
||||
};
|
||||
|
||||
useNuiEvent("loadLocales", (data) => setLanguages(data));
|
||||
useNuiEvent("openSettings", (data: string) => {
|
||||
useNuiEvent('loadLocales', (data) => setLanguages(data));
|
||||
useNuiEvent('openSettings', (data: string) => {
|
||||
onOpen();
|
||||
setSelectValue(data);
|
||||
});
|
||||
@@ -46,11 +46,7 @@ const Settings: React.FC = () => {
|
||||
<ModalContent>
|
||||
<ModalHeader>{locale.ui.settings.title}</ModalHeader>
|
||||
<ModalBody>
|
||||
<LocaleSetting
|
||||
languages={languages}
|
||||
selectValue={selectValue}
|
||||
setSelectValue={setSelectValue}
|
||||
/>
|
||||
<LocaleSetting languages={languages} selectValue={selectValue} setSelectValue={setSelectValue} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button colorScheme="blue" onClick={closeSettings}>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from "react";
|
||||
import { useNuiEvent } from "../../hooks/useNuiEvent";
|
||||
import { Box, Flex, ScaleFade } from "@chakra-ui/react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import React from 'react';
|
||||
import { useNuiEvent } from '../../hooks/useNuiEvent';
|
||||
import { Box, Flex, ScaleFade } from '@chakra-ui/react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
|
||||
export interface TextUiProps {
|
||||
text: string;
|
||||
position?: "right-center" | "left-center" | "top-center";
|
||||
position?: 'right-center' | 'left-center' | 'top-center';
|
||||
icon?: IconProp;
|
||||
iconColor?: string;
|
||||
style?: React.CSSProperties;
|
||||
@@ -15,18 +15,18 @@ export interface TextUiProps {
|
||||
|
||||
const TextUI: React.FC = () => {
|
||||
const [data, setData] = React.useState<TextUiProps>({
|
||||
text: "",
|
||||
position: "right-center",
|
||||
text: '',
|
||||
position: 'right-center',
|
||||
});
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
|
||||
useNuiEvent<TextUiProps>("textUi", (data) => {
|
||||
if (!data.position) data.position = "right-center"; // Default right position
|
||||
useNuiEvent<TextUiProps>('textUi', (data) => {
|
||||
if (!data.position) data.position = 'right-center'; // Default right position
|
||||
setData(data);
|
||||
setVisible(true);
|
||||
});
|
||||
|
||||
useNuiEvent("textUiHide", () => setVisible(false));
|
||||
useNuiEvent('textUiHide', () => setVisible(false));
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -34,13 +34,9 @@ const TextUI: React.FC = () => {
|
||||
h="100%"
|
||||
p={3}
|
||||
position="absolute"
|
||||
alignItems={data.position === "top-center" ? "baseline" : "center"}
|
||||
alignItems={data.position === 'top-center' ? 'baseline' : 'center'}
|
||||
justifyContent={
|
||||
data.position === "right-center"
|
||||
? "flex-end"
|
||||
: data.position === "left-center"
|
||||
? "flex-start"
|
||||
: "center"
|
||||
data.position === 'right-center' ? 'flex-end' : data.position === 'left-center' ? 'flex-start' : 'center'
|
||||
}
|
||||
>
|
||||
<ScaleFade in={visible} unmountOnExit>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
export const useKeyPress = (targetKey: KeyboardEvent["key"]) => {
|
||||
export const useKeyPress = (targetKey: KeyboardEvent['key']) => {
|
||||
const [keyPressed, setKeyPressed] = React.useState(false);
|
||||
|
||||
const downHandler = React.useCallback(
|
||||
@@ -22,12 +22,12 @@ export const useKeyPress = (targetKey: KeyboardEvent["key"]) => {
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener("keydown", downHandler);
|
||||
window.addEventListener("keyup", upHandler);
|
||||
window.addEventListener('keydown', downHandler);
|
||||
window.addEventListener('keyup', upHandler);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", downHandler);
|
||||
window.removeEventListener("keyup", upHandler);
|
||||
window.removeEventListener('keydown', downHandler);
|
||||
window.removeEventListener('keyup', upHandler);
|
||||
};
|
||||
}, [downHandler, upHandler]);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {MutableRefObject, useEffect, useRef} from "react";
|
||||
import {noop} from "../utils/misc";
|
||||
import { MutableRefObject, useEffect, useRef } from 'react';
|
||||
import { noop } from '../utils/misc';
|
||||
|
||||
interface NuiMessageData<T = unknown> {
|
||||
action: string;
|
||||
@@ -20,10 +20,7 @@ type NuiHandlerSignature<T> = (data: T) => void;
|
||||
*
|
||||
**/
|
||||
|
||||
export const useNuiEvent = <T = any>(
|
||||
action: string,
|
||||
handler: (data: T) => void
|
||||
) => {
|
||||
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
|
||||
@@ -42,8 +39,8 @@ export const useNuiEvent = <T = any>(
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", eventListener);
|
||||
window.addEventListener('message', eventListener);
|
||||
// Remove Event Listener on component cleanup
|
||||
return () => window.removeEventListener("message", eventListener);
|
||||
return () => window.removeEventListener('message', eventListener);
|
||||
}, [action]);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@200;400;500;700&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Fira+Mono&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap");
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Fira+Mono&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
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;
|
||||
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;
|
||||
@@ -21,7 +21,7 @@ body {
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
@keyframes progress {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
|
||||
export interface Option {
|
||||
menu?: string;
|
||||
@@ -8,10 +8,7 @@ export interface Option {
|
||||
image?: string;
|
||||
icon?: IconProp;
|
||||
iconColor?: string;
|
||||
metadata?:
|
||||
| string[]
|
||||
| { [key: string]: any }
|
||||
| { label: string; value: any }[];
|
||||
metadata?: string[] | { [key: string]: any } | { label: string; value: any }[];
|
||||
event?: string;
|
||||
serverEvent?: string;
|
||||
args?: any;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
|
||||
export interface IInput {
|
||||
type: "input",
|
||||
type: 'input';
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
default?: string;
|
||||
@@ -10,27 +10,29 @@ export interface IInput {
|
||||
}
|
||||
|
||||
export interface ICheckbox {
|
||||
type: "checkbox",
|
||||
type: 'checkbox';
|
||||
label: string;
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export interface ISelect {
|
||||
type: "select",
|
||||
type: 'select';
|
||||
label: string;
|
||||
default?: string;
|
||||
options?: { value: string; label?: string }[];
|
||||
}
|
||||
|
||||
export interface INumber {
|
||||
type: "number",
|
||||
type: 'number';
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
default?: number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
export interface ISlider {
|
||||
type: "slider",
|
||||
type: 'slider';
|
||||
label: string;
|
||||
default?: number;
|
||||
min?: number;
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import { VisibilityProvider } from "./providers/VisibilityProvider";
|
||||
import { ChakraProvider } from "@chakra-ui/react";
|
||||
import { theme } from "./theme";
|
||||
import { debugData } from "./utils/debugData";
|
||||
import { fas } from "@fortawesome/free-solid-svg-icons";
|
||||
import { far } from "@fortawesome/free-regular-svg-icons";
|
||||
import { fab } from "@fortawesome/free-brands-svg-icons";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { isEnvBrowser } from "./utils/misc";
|
||||
import LocaleProvider from "./providers/LocaleProvider";
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { VisibilityProvider } from './providers/VisibilityProvider';
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
import { theme } from './theme';
|
||||
import { debugData } from './utils/debugData';
|
||||
import { fas } from '@fortawesome/free-solid-svg-icons';
|
||||
import { far } from '@fortawesome/free-regular-svg-icons';
|
||||
import { fab } from '@fortawesome/free-brands-svg-icons';
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { isEnvBrowser } from './utils/misc';
|
||||
import LocaleProvider from './providers/LocaleProvider';
|
||||
|
||||
library.add(fas, far, fab);
|
||||
|
||||
if (isEnvBrowser()) {
|
||||
const root = document.getElementById("root");
|
||||
const root = document.getElementById('root');
|
||||
|
||||
// https://i.imgur.com/iPTAdYV.png - Night time img
|
||||
root!.style.backgroundImage = 'url("https://i.imgur.com/3pzRj9n.png")';
|
||||
root!.style.backgroundSize = "cover";
|
||||
root!.style.backgroundRepeat = "no-repeat";
|
||||
root!.style.backgroundPosition = "center";
|
||||
root!.style.backgroundSize = 'cover';
|
||||
root!.style.backgroundRepeat = 'no-repeat';
|
||||
root!.style.backgroundPosition = 'center';
|
||||
}
|
||||
|
||||
debugData([
|
||||
{
|
||||
action: "setVisible",
|
||||
action: 'setVisible',
|
||||
data: true,
|
||||
},
|
||||
]);
|
||||
@@ -42,5 +42,5 @@ ReactDOM.render(
|
||||
</VisibilityProvider>
|
||||
</LocaleProvider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById("root")
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
@@ -1,40 +1,26 @@
|
||||
import { Context, createContext, useContext, useState } from "react";
|
||||
import { useNuiEvent } from "../hooks/useNuiEvent";
|
||||
import { debugData } from "../utils/debugData";
|
||||
import { Context, createContext, useContext, useState } from 'react';
|
||||
import { useNuiEvent } from '../hooks/useNuiEvent';
|
||||
import { debugData } from '../utils/debugData';
|
||||
|
||||
debugData([
|
||||
{
|
||||
action: "loadLocales",
|
||||
data: [
|
||||
"en",
|
||||
"fr",
|
||||
"de",
|
||||
"it",
|
||||
"es",
|
||||
"pt-BR",
|
||||
"pl",
|
||||
"ru",
|
||||
"ko",
|
||||
"zh-TW",
|
||||
"ja",
|
||||
"es-MX",
|
||||
"zh-CN",
|
||||
],
|
||||
action: 'loadLocales',
|
||||
data: ['en', 'fr', 'de', 'it', 'es', 'pt-BR', 'pl', 'ru', 'ko', 'zh-TW', 'ja', 'es-MX', 'zh-CN'],
|
||||
},
|
||||
]);
|
||||
|
||||
debugData([
|
||||
{
|
||||
action: "setLocale",
|
||||
action: 'setLocale',
|
||||
data: {
|
||||
language: "English",
|
||||
language: 'English',
|
||||
ui: {
|
||||
settings: {
|
||||
title: "Settings",
|
||||
language: "Language",
|
||||
title: 'Settings',
|
||||
language: 'Language',
|
||||
},
|
||||
close: "Close",
|
||||
confirm: "Confirm",
|
||||
close: 'Close',
|
||||
confirm: 'Confirm',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -62,28 +48,23 @@ const LocaleCtx = createContext<LocaleContextValue | null>(null);
|
||||
|
||||
const LocaleProvider: React.FC = ({ children }) => {
|
||||
const [locale, setLocale] = useState<Locale>({
|
||||
language: "",
|
||||
language: '',
|
||||
ui: {
|
||||
settings: {
|
||||
title: "",
|
||||
language: "",
|
||||
title: '',
|
||||
language: '',
|
||||
},
|
||||
cancel: "",
|
||||
close: "",
|
||||
confirm: "",
|
||||
cancel: '',
|
||||
close: '',
|
||||
confirm: '',
|
||||
},
|
||||
});
|
||||
|
||||
useNuiEvent("setLocale", async (data: Locale) => setLocale(data));
|
||||
useNuiEvent('setLocale', async (data: Locale) => setLocale(data));
|
||||
|
||||
return (
|
||||
<LocaleCtx.Provider value={{ locale, setLocale }}>
|
||||
{children}
|
||||
</LocaleCtx.Provider>
|
||||
);
|
||||
return <LocaleCtx.Provider value={{ locale, setLocale }}>{children}</LocaleCtx.Provider>;
|
||||
};
|
||||
|
||||
export default LocaleProvider;
|
||||
|
||||
export const useLocales = () =>
|
||||
useContext<LocaleContextValue>(LocaleCtx as Context<LocaleContextValue>);
|
||||
export const useLocales = () => useContext<LocaleContextValue>(LocaleCtx as Context<LocaleContextValue>);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Context, createContext, useContext, useState } from "react";
|
||||
import { useNuiEvent } from "../hooks/useNuiEvent";
|
||||
import React, { Context, createContext, useContext, useState } from 'react';
|
||||
import { useNuiEvent } from '../hooks/useNuiEvent';
|
||||
|
||||
const VisibilityCtx = createContext<VisibilityProviderValue | null>(null);
|
||||
|
||||
@@ -13,7 +13,7 @@ interface VisibilityProviderValue {
|
||||
export const VisibilityProvider: React.FC = ({ children }) => {
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
useNuiEvent<boolean>("setVisible", setVisible);
|
||||
useNuiEvent<boolean>('setVisible', setVisible);
|
||||
|
||||
return (
|
||||
<VisibilityCtx.Provider
|
||||
@@ -22,16 +22,10 @@ export const VisibilityProvider: React.FC = ({ children }) => {
|
||||
setVisible,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ visibility: visible ? "visible" : "hidden", height: "100%" }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<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>);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { extendTheme, type ThemeConfig } from "@chakra-ui/react";
|
||||
import { extendTheme, type ThemeConfig } from '@chakra-ui/react';
|
||||
|
||||
const config: ThemeConfig = {
|
||||
initialColorMode: 'dark',
|
||||
useSystemColorMode: false
|
||||
}
|
||||
initialColorMode: 'dark',
|
||||
useSystemColorMode: false,
|
||||
};
|
||||
|
||||
export const theme = extendTheme({
|
||||
config,
|
||||
})
|
||||
config,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {isEnvBrowser} from "./misc";
|
||||
import { isEnvBrowser } from './misc';
|
||||
|
||||
interface DebugEvent<T = any> {
|
||||
action: string;
|
||||
@@ -13,11 +13,11 @@ interface DebugEvent<T = any> {
|
||||
* @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()) {
|
||||
if (process.env.NODE_ENV === 'development' && isEnvBrowser()) {
|
||||
for (const event of events) {
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(
|
||||
new MessageEvent("message", {
|
||||
new MessageEvent('message', {
|
||||
data: {
|
||||
action: event.action,
|
||||
data: event.data,
|
||||
|
||||
@@ -18,11 +18,13 @@ export async function fetchNui<T = any>(eventName: string, data?: any): Promise<
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
|
||||
const resourceName = (window as any).GetParentResourceName ? (window as any).GetParentResourceName() : 'nui-frame-app';
|
||||
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()
|
||||
const respFormatted = await resp.json();
|
||||
|
||||
return respFormatted
|
||||
return respFormatted;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Will return whether the current environment is in a regular browser
|
||||
// and not CEF
|
||||
export const isEnvBrowser = (): boolean => !(window as any).invokeNative
|
||||
export const isEnvBrowser = (): boolean => !(window as any).invokeNative;
|
||||
|
||||
// Basic no operation function
|
||||
export const noop = () => {}
|
||||
export const noop = () => {};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// yoinked from https://github.com/project-error/npwd/blob/d8dc5b7f47faf5fc581ffee30f31ff61d184cfe7/phone/src/os/phone/hooks/useClipboard.ts#L1
|
||||
export const setClipboard = (value: string) => {
|
||||
const clipElem = document.createElement('input');
|
||||
clipElem.value = value;
|
||||
document.body.appendChild(clipElem);
|
||||
clipElem.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(clipElem);
|
||||
const clipElem = document.createElement('input');
|
||||
clipElem.value = value;
|
||||
document.body.appendChild(clipElem);
|
||||
clipElem.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(clipElem);
|
||||
};
|
||||
Reference in New Issue
Block a user