chore(web): add prettier and format

This commit is contained in:
Luke
2022-08-27 15:58:36 +02:00
parent cc76d23419
commit 4a5251b60b
44 changed files with 2933 additions and 1984 deletions

View File

@@ -22,6 +22,7 @@
"@vitejs/plugin-react": "^1.3.2", "@vitejs/plugin-react": "^1.3.2",
"focus-trap-react": "^9.0.2", "focus-trap-react": "^9.0.2",
"framer-motion": "^6.2.8", "framer-motion": "^6.2.8",
"prettier": "^2.7.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-markdown": "^8.0.1", "react-markdown": "^8.0.1",

3682
web/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

9
web/src/.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"printWidth": 120,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"bracketSpacing": true,
"trailingComma": "es5"
}

View File

@@ -1,24 +1,24 @@
import Notifications from "./features/notifications/NotificationWrapper"; import Notifications from './features/notifications/NotificationWrapper';
import CircleProgressbar from "./features/progress/CircleProgressbar"; import CircleProgressbar from './features/progress/CircleProgressbar';
import Progressbar from "./features/progress/Progressbar"; import Progressbar from './features/progress/Progressbar';
import TextUI from "./features/textui/TextUI"; import TextUI from './features/textui/TextUI';
import InputDialog from "./features/dialog/InputDialog"; import InputDialog from './features/dialog/InputDialog';
import ContextMenu from "./features/menu/context/ContextMenu"; import ContextMenu from './features/menu/context/ContextMenu';
import Settings from "./features/settings"; import Settings from './features/settings';
import { useNuiEvent } from "./hooks/useNuiEvent"; import { useNuiEvent } from './hooks/useNuiEvent';
import { setClipboard } from "./utils/setClipboard"; import { setClipboard } from './utils/setClipboard';
import { fetchNui } from "./utils/fetchNui"; import { fetchNui } from './utils/fetchNui';
import AlertDialog from "./features/dialog/AlertDialog"; import AlertDialog from './features/dialog/AlertDialog';
import ListMenu from "./features/menu/list"; import ListMenu from './features/menu/list';
import Dev from "./features/dev"; import Dev from './features/dev';
import { isEnvBrowser } from "./utils/misc"; import { isEnvBrowser } from './utils/misc';
const App: React.FC = () => { const App: React.FC = () => {
useNuiEvent("setClipboard", (data: string) => { useNuiEvent('setClipboard', (data: string) => {
setClipboard(data); setClipboard(data);
}); });
fetchNui("init"); fetchNui('init');
return ( return (
<> <>

View File

@@ -1,13 +1,13 @@
import { debugData } from "../../../utils/debugData"; import { debugData } from '../../../utils/debugData';
import { AlertProps } from "../../dialog/AlertDialog"; import { AlertProps } from '../../dialog/AlertDialog';
export const debugAlert = () => { export const debugAlert = () => {
debugData<AlertProps>([ debugData<AlertProps>([
{ {
action: "sendAlert", action: 'sendAlert',
data: { data: {
header: "Hello there", header: 'Hello there',
content: "General kenobi \n Markdown works", content: 'General kenobi \n Markdown works',
centered: true, centered: true,
cancel: true, cancel: true,
}, },

View File

@@ -1,35 +1,35 @@
import { ContextMenuProps } from "../../../interfaces/context"; import { ContextMenuProps } from '../../../interfaces/context';
import { debugData } from "../../../utils/debugData"; import { debugData } from '../../../utils/debugData';
export const debugContext = () => { export const debugContext = () => {
debugData<ContextMenuProps>([ debugData<ContextMenuProps>([
{ {
action: "showContext", action: 'showContext',
data: { data: {
title: "Vehicle garage", title: 'Vehicle garage',
options: [ options: [
{ title: "Empty button" }, { title: 'Empty button' },
{ {
title: "Example button", title: 'Example button',
description: "Example button description", description: 'Example button description',
icon: "inbox", icon: 'inbox',
image: "https://i.imgur.com/YAe7k17.jpeg", image: 'https://i.imgur.com/YAe7k17.jpeg',
metadata: [{ label: "Value 1", value: 300 }], metadata: [{ label: 'Value 1', value: 300 }],
}, },
{ {
title: "Menu button", title: 'Menu button',
icon: "bars", icon: 'bars',
menu: "other_example_menu", menu: 'other_example_menu',
description: "Takes you to another menu", description: 'Takes you to another menu',
metadata: ["It also has metadata support"], metadata: ['It also has metadata support'],
}, },
{ {
title: "Event button", title: 'Event button',
description: "Open a menu and send event data", description: 'Open a menu and send event data',
icon: "check", icon: 'check',
arrow: true, arrow: true,
event: "some_event", event: 'some_event',
args: { value1: 300, value2: "Other value" }, args: { value1: 300, value2: 'Other value' },
}, },
], ],
}, },

View File

@@ -1,34 +1,34 @@
import { debugData } from "../../../utils/debugData"; import { debugData } from '../../../utils/debugData';
import { InputProps } from "../../dialog/InputDialog"; import { InputProps } from '../../dialog/InputDialog';
export const debugInput = () => { export const debugInput = () => {
debugData<InputProps>([ debugData<InputProps>([
{ {
action: "openDialog", action: 'openDialog',
data: { data: {
heading: "Police locker", heading: 'Police locker',
rows: [ rows: [
{ type: "input", label: "Locker number", placeholder: "420" }, { type: 'input', label: 'Locker number', placeholder: '420' },
{ type: "checkbox", label: "Some checkbox" }, { type: 'checkbox', label: 'Some checkbox' },
{ type: "input", label: "Locker PIN", password: true, icon: "lock" }, { type: 'input', label: 'Locker PIN', password: true, icon: 'lock' },
{ type: "checkbox", label: "Some other checkbox", checked: true }, { type: 'checkbox', label: 'Some other checkbox', checked: true },
{ {
type: "select", type: 'select',
label: "Locker type", label: 'Locker type',
options: [ options: [
{ value: "option1" }, { value: 'option1' },
{ value: "option2", label: "Option 2" }, { value: 'option2', label: 'Option 2' },
{ value: "option3", label: "Option 3" }, { value: 'option3', label: 'Option 3' },
], ],
}, },
{ {
type: "number", type: 'number',
label: "Number counter", label: 'Number counter',
default: 12, default: 12,
min: 3, min: 3,
max: 10, max: 10,
}, },
{ type: "slider", label: "Slide bar", min: 10, max: 50, step: 2 }, { type: 'slider', label: 'Slide bar', min: 10, max: 50, step: 2 },
], ],
}, },
}, },

View File

@@ -1,38 +1,38 @@
import { debugData } from "../../../utils/debugData"; import { debugData } from '../../../utils/debugData';
import { MenuSettings } from "../../menu/list"; import { MenuSettings } from '../../menu/list';
export const debugMenu = () => { export const debugMenu = () => {
debugData<MenuSettings>([ debugData<MenuSettings>([
{ {
action: "setMenu", action: 'setMenu',
data: { data: {
// position: "bottom-left", // position: "bottom-left",
title: "Vehicle garage", title: 'Vehicle garage',
items: [ items: [
{ label: "Option 1", icon: "heart" }, { label: 'Option 1', icon: 'heart' },
{ {
label: "Option 2", label: 'Option 2',
icon: "basket-shopping", icon: 'basket-shopping',
description: "Tooltip description 1", description: 'Tooltip description 1',
}, },
{ {
label: "Vehicle class", label: 'Vehicle class',
values: ["Nice", "Super nice", "Extra nice"], values: ['Nice', 'Super nice', 'Extra nice'],
icon: "tag", icon: 'tag',
description: "Tooltip description 2", description: 'Tooltip description 2',
}, },
{ label: "Option 1" }, { label: 'Option 1' },
{ label: "Option 2" }, { label: 'Option 2' },
{ {
label: "Vehicle class", label: 'Vehicle class',
values: ["Nice", "Super nice", "Extra nice"], values: ['Nice', 'Super nice', 'Extra nice'],
defaultIndex: 1, defaultIndex: 1,
}, },
{ label: "Option 1" }, { label: 'Option 1' },
{ label: "Option 2" }, { label: 'Option 2' },
{ {
label: "Vehicle class", label: 'Vehicle class',
values: ["Nice", "Super nice", "Extra nice"], values: ['Nice', 'Super nice', 'Extra nice'],
}, },
], ],
}, },

View File

@@ -1,16 +1,13 @@
import { import { CustomNotificationProps, NotificationProps } from '../../notifications/NotificationWrapper';
CustomNotificationProps, import { debugData } from '../../../utils/debugData';
NotificationProps,
} from "../../notifications/NotificationWrapper";
import { debugData } from "../../../utils/debugData";
export const debugNotification = () => { export const debugNotification = () => {
debugData<NotificationProps>([ debugData<NotificationProps>([
{ {
action: "notify", action: 'notify',
data: { data: {
description: "Dunak is nerd", description: 'Dunak is nerd',
title: "Dunak", title: 'Dunak',
id: 1, id: 1,
}, },
}, },
@@ -20,13 +17,13 @@ export const debugNotification = () => {
export const debugCustomNotification = () => { export const debugCustomNotification = () => {
debugData<CustomNotificationProps>([ debugData<CustomNotificationProps>([
{ {
action: "customNotify", action: 'customNotify',
data: { data: {
description: "Dunak is nerd", description: 'Dunak is nerd',
icon: "basket-shopping", icon: 'basket-shopping',
style: { style: {
backgroundColor: "#2D3748", backgroundColor: '#2D3748',
color: "white", color: 'white',
}, },
}, },
}, },

View File

@@ -1,12 +1,12 @@
import { debugData } from "../../../utils/debugData"; import { debugData } from '../../../utils/debugData';
import { ProgressbarProps } from "../../progress/Progressbar"; import { ProgressbarProps } from '../../progress/Progressbar';
export const debugProgressbar = () => { export const debugProgressbar = () => {
debugData<ProgressbarProps>([ debugData<ProgressbarProps>([
{ {
action: "progress", action: 'progress',
data: { data: {
label: "Using Lockpick", label: 'Using Lockpick',
duration: 8000, duration: 8000,
}, },
}, },
@@ -16,10 +16,10 @@ export const debugProgressbar = () => {
export const debugCircleProgressbar = () => { export const debugCircleProgressbar = () => {
debugData([ debugData([
{ {
action: "circleProgress", action: 'circleProgress',
data: { data: {
duration: 8000, duration: 8000,
label: "Using Armour", label: 'Using Armour',
}, },
}, },
]); ]);

View File

@@ -1,10 +1,10 @@
import { debugData } from "../../../utils/debugData"; import { debugData } from '../../../utils/debugData';
export const debugSettings = () => { export const debugSettings = () => {
debugData([ debugData([
{ {
action: "openSettings", action: 'openSettings',
data: true, data: true,
}, },
]); ]);
} };

View File

@@ -1,14 +1,14 @@
import { TextUiProps } from "../../textui/TextUI"; import { TextUiProps } from '../../textui/TextUI';
import { debugData } from "../../../utils/debugData"; import { debugData } from '../../../utils/debugData';
export const debugTextUI = () => { export const debugTextUI = () => {
debugData<TextUiProps>([ debugData<TextUiProps>([
{ {
action: "textUi", action: 'textUi',
data: { data: {
text: "[E] - Access locker inventory \n [G] - Do something else", text: '[E] - Access locker inventory \n [G] - Do something else',
position: "right-center", position: 'right-center',
icon: "door-open", icon: 'door-open',
}, },
}, },
]); ]);

View File

@@ -10,19 +10,16 @@ import {
VStack, VStack,
Divider, Divider,
useDisclosure, useDisclosure,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { debugAlert } from "./debug/alert"; import { debugAlert } from './debug/alert';
import { debugContext } from "./debug/context"; import { debugContext } from './debug/context';
import { debugInput } from "./debug/input"; import { debugInput } from './debug/input';
import { debugMenu } from "./debug/menu"; import { debugMenu } from './debug/menu';
import { import { debugCustomNotification, debugNotification } from './debug/notification';
debugCustomNotification, import { debugCircleProgressbar, debugProgressbar } from './debug/progress';
debugNotification, import { debugTextUI } from './debug/textui';
} from "./debug/notification"; import { debugSettings } from './debug/settings';
import { debugCircleProgressbar, debugProgressbar } from "./debug/progress";
import { debugTextUI } from "./debug/textui";
import { debugSettings } from "./debug/settings";
const Dev: React.FC = () => { const Dev: React.FC = () => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();

View File

@@ -7,12 +7,12 @@ import {
AlertDialogOverlay, AlertDialogOverlay,
useDisclosure, useDisclosure,
Button, Button,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { useRef, useState } from "react"; import { useRef, useState } from 'react';
import ReactMarkdown from "react-markdown"; import ReactMarkdown from 'react-markdown';
import { useNuiEvent } from "../../hooks/useNuiEvent"; import { useNuiEvent } from '../../hooks/useNuiEvent';
import { fetchNui } from "../../utils/fetchNui"; import { fetchNui } from '../../utils/fetchNui';
import { useLocales } from "../../providers/LocaleProvider"; import { useLocales } from '../../providers/LocaleProvider';
export interface AlertProps { export interface AlertProps {
header: string; header: string;
@@ -26,16 +26,16 @@ const AlertDialog: React.FC = () => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = useRef(null); const cancelRef = useRef(null);
const [dialogData, setDialogData] = useState<AlertProps>({ const [dialogData, setDialogData] = useState<AlertProps>({
header: "", header: '',
content: "", content: '',
}); });
const closeAlert = (button: string) => { const closeAlert = (button: string) => {
onClose(); onClose();
fetchNui("closeAlert", button); fetchNui('closeAlert', button);
}; };
useNuiEvent("sendAlert", (data: AlertProps) => { useNuiEvent('sendAlert', (data: AlertProps) => {
setDialogData(data); setDialogData(data);
onOpen(); onOpen();
}); });
@@ -48,7 +48,7 @@ const AlertDialog: React.FC = () => {
isOpen={isOpen} isOpen={isOpen}
isCentered={dialogData.centered} isCentered={dialogData.centered}
closeOnOverlayClick={false} closeOnOverlayClick={false}
onEsc={() => closeAlert("cancel")} onEsc={() => closeAlert('cancel')}
> >
<AlertDialogOverlay /> <AlertDialogOverlay />
<AlertDialogContent fontFamily="Inter"> <AlertDialogContent fontFamily="Inter">
@@ -60,14 +60,11 @@ const AlertDialog: React.FC = () => {
</AlertDialogBody> </AlertDialogBody>
<AlertDialogFooter> <AlertDialogFooter>
{dialogData.cancel && ( {dialogData.cancel && (
<Button onClick={() => closeAlert("cancel")} mr={3}> <Button onClick={() => closeAlert('cancel')} mr={3}>
{locale.ui.cancel} {locale.ui.cancel}
</Button> </Button>
)} )}
<Button <Button colorScheme={dialogData.cancel ? 'blue' : undefined} onClick={() => closeAlert('confirm')}>
colorScheme={dialogData.cancel ? "blue" : undefined}
onClick={() => closeAlert("confirm")}
>
{locale.ui.confirm} {locale.ui.confirm}
</Button> </Button>
</AlertDialogFooter> </AlertDialogFooter>

View File

@@ -1,28 +1,14 @@
import { import { Modal, ModalOverlay, ModalContent, ModalFooter, ModalHeader, ModalBody, Button } from '@chakra-ui/react';
Modal, import React from 'react';
ModalOverlay, import { useNuiEvent } from '../../hooks/useNuiEvent';
ModalContent, import { useLocales } from '../../providers/LocaleProvider';
ModalFooter, import { fetchNui } from '../../utils/fetchNui';
ModalHeader, import { IInput, ICheckbox, ISelect, INumber, ISlider } from '../../interfaces/dialog';
ModalBody, import InputField from './components/input';
Button, import CheckboxField from './components/checkbox';
} from "@chakra-ui/react"; import SelectField from './components/select';
import React from "react"; import NumberField from './components/number';
import { useNuiEvent } from "../../hooks/useNuiEvent"; import SliderField from './components/slider';
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 { export interface InputProps {
heading: string; heading: string;
@@ -31,12 +17,10 @@ export interface InputProps {
const InputDialog: React.FC = () => { const InputDialog: React.FC = () => {
const [fields, setFields] = React.useState<InputProps>({ const [fields, setFields] = React.useState<InputProps>({
heading: "", heading: '',
rows: [{ type: "input", label: "" }], rows: [{ type: 'input', label: '' }],
}); });
const [inputData, setInputData] = React.useState< const [inputData, setInputData] = React.useState<Array<string | number | boolean>>([]);
Array<string | number | boolean>
>([]);
const [passwordStates, setPasswordStates] = React.useState<boolean[]>([]); const [passwordStates, setPasswordStates] = React.useState<boolean[]>([]);
const [visible, setVisible] = React.useState(false); const [visible, setVisible] = React.useState(false);
const { locale } = useLocales(); const { locale } = useLocales();
@@ -48,7 +32,7 @@ const InputDialog: React.FC = () => {
}); });
}; };
useNuiEvent<InputProps>("openDialog", (data) => { useNuiEvent<InputProps>('openDialog', (data) => {
setPasswordStates([]); setPasswordStates([]);
setFields(data); setFields(data);
setInputData([]); setInputData([]);
@@ -57,7 +41,7 @@ const InputDialog: React.FC = () => {
const handleClose = () => { const handleClose = () => {
setVisible(false); setVisible(false);
fetchNui("inputData"); fetchNui('inputData');
}; };
const handleChange = (value: string | number | boolean, index: number) => { const handleChange = (value: string | number | boolean, index: number) => {
@@ -69,34 +53,23 @@ const InputDialog: React.FC = () => {
const handleConfirm = () => { const handleConfirm = () => {
setVisible(false); setVisible(false);
fetchNui("inputData", inputData); fetchNui('inputData', inputData);
}; };
return ( return (
<> <>
<Modal <Modal isOpen={visible} onClose={handleClose} isCentered closeOnEsc closeOnOverlayClick={false} size="xs">
isOpen={visible}
onClose={handleClose}
isCentered
closeOnEsc
closeOnOverlayClick={false}
size="xs"
>
<ModalOverlay /> <ModalOverlay />
<ModalContent <ModalContent
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter" && visible) return handleConfirm(); if (e.key === 'Enter' && visible) return handleConfirm();
}} }}
> >
<ModalHeader textAlign="center">{fields.heading}</ModalHeader> <ModalHeader textAlign="center">{fields.heading}</ModalHeader>
<ModalBody fontFamily="Poppins" textAlign="left"> <ModalBody fontFamily="Poppins" textAlign="left">
{fields.rows.map( {fields.rows.map((row: IInput | ICheckbox | ISelect | INumber | ISlider, index) => (
(
row: IInput | ICheckbox | ISelect | INumber | ISlider,
index
) => (
<React.Fragment key={`row-${index}`}> <React.Fragment key={`row-${index}`}>
{row.type === "input" && ( {row.type === 'input' && (
<InputField <InputField
key={`input-${index}`} key={`input-${index}`}
row={row} row={row}
@@ -106,37 +79,12 @@ const InputDialog: React.FC = () => {
handlePasswordStates={handlePasswordStates} handlePasswordStates={handlePasswordStates}
/> />
)} )}
{row.type === "checkbox" && ( {row.type === 'checkbox' && <CheckboxField row={row} index={index} handleChange={handleChange} />}
<CheckboxField {row.type === 'select' && <SelectField row={row} index={index} handleChange={handleChange} />}
row={row} {row.type === 'number' && <NumberField row={row} index={index} handleChange={handleChange} />}
index={index} {row.type === 'slider' && <SliderField row={row} index={index} handleChange={handleChange} />}
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> </React.Fragment>
) ))}
)}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button mr={3} onClick={handleClose}> <Button mr={3} onClick={handleClose}>

View File

@@ -1,6 +1,6 @@
import { Box, Checkbox } from "@chakra-ui/react"; import { Box, Checkbox } from '@chakra-ui/react';
import { useEffect } from "react"; import { useEffect } from 'react';
import { ICheckbox } from "../../../interfaces/dialog"; import { ICheckbox } from '../../../interfaces/dialog';
interface Props { interface Props {
row: ICheckbox; row: ICheckbox;

View File

@@ -1,14 +1,7 @@
import { import { Box, InputGroup, InputLeftElement, InputRightElement, Text, Input } from '@chakra-ui/react';
Box, import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
InputGroup, import { useEffect } from 'react';
InputLeftElement, import { IInput } from '../../../interfaces/dialog';
InputRightElement,
Text,
Input,
} from "@chakra-ui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useEffect } from "react";
import { IInput } from "../../../interfaces/dialog";
interface Props { interface Props {
row: IInput; row: IInput;
@@ -29,20 +22,13 @@ const InputField: React.FC<Props> = (props) => {
<Text>{props.row.label}</Text> <Text>{props.row.label}</Text>
<InputGroup> <InputGroup>
{props.row.icon && ( {props.row.icon && (
<InputLeftElement <InputLeftElement pointerEvents="none" children={<FontAwesomeIcon icon={props.row.icon} />} />
pointerEvents="none"
children={<FontAwesomeIcon icon={props.row.icon} />}
/>
)} )}
<Input <Input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => props.handleChange(e.target.value, props.index)} onChange={(e: React.ChangeEvent<HTMLInputElement>) => props.handleChange(e.target.value, props.index)}
placeholder={props.row.placeholder} placeholder={props.row.placeholder}
defaultValue={props.row.default} defaultValue={props.row.default}
type={ type={!props.row.password || props.passwordStates[props.index] ? 'text' : 'password'}
!props.row.password || props.passwordStates[props.index]
? "text"
: "password"
}
/> />
{props.row.password && ( {props.row.password && (
<InputRightElement <InputRightElement
@@ -51,7 +37,7 @@ const InputField: React.FC<Props> = (props) => {
children={ children={
<FontAwesomeIcon <FontAwesomeIcon
fixedWidth fixedWidth
icon={props.passwordStates[props.index] ? "eye" : "eye-slash"} icon={props.passwordStates[props.index] ? 'eye' : 'eye-slash'}
fontSize="1em" fontSize="1em"
style={{ paddingRight: 8 }} style={{ paddingRight: 8 }}
/> />

View File

@@ -6,9 +6,9 @@ import {
NumberInputStepper, NumberInputStepper,
NumberIncrementStepper, NumberIncrementStepper,
NumberDecrementStepper, NumberDecrementStepper,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { useEffect } from "react"; import { useEffect } from 'react';
import { INumber } from "../../../interfaces/dialog"; import { INumber } from '../../../interfaces/dialog';
interface Props { interface Props {
row: INumber; row: INumber;

View File

@@ -1,6 +1,6 @@
import { Box, Select } from "@chakra-ui/react"; import { Box, Select } from '@chakra-ui/react';
import { useEffect } from "react"; import { useEffect } from 'react';
import { ISelect } from "../../../interfaces/dialog"; import { ISelect } from '../../../interfaces/dialog';
interface Props { interface Props {
row: ISelect; row: ISelect;
@@ -24,12 +24,14 @@ const SelectField: React.FC<Props> = (props) => {
<Box mb={3} key={`select-${props.index}`}> <Box mb={3} key={`select-${props.index}`}>
<Select <Select
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => props.handleChange(e.target.value, props.index)} 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 */} {/* Hacky workaround for selectable placeholder issue */}
{!props.row.default && (<option value="" hidden disabled> {!props.row.default && (
<option value="" hidden disabled>
{props.row.label} {props.row.label}
</option>)} </option>
)}
{props.row.options?.map((option, index) => ( {props.row.options?.map((option, index) => (
<option key={`option-${index}`} value={option.value}> <option key={`option-${index}`} value={option.value}>
{option.label || option.value} {option.label || option.value}

View File

@@ -1,6 +1,6 @@
import { Box, Text, Slider, SliderTrack, SliderFilledTrack, SliderThumb, HStack, Tooltip } from "@chakra-ui/react"; import { Box, Text, Slider, SliderTrack, SliderFilledTrack, SliderThumb, HStack, Tooltip } from '@chakra-ui/react';
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react';
import { ISlider } from "../../../interfaces/dialog"; import { ISlider } from '../../../interfaces/dialog';
interface Props { interface Props {
row: ISlider; row: ISlider;
@@ -30,12 +30,7 @@ const SliderField: React.FC<Props> = (props) => {
<SliderTrack> <SliderTrack>
<SliderFilledTrack /> <SliderFilledTrack />
</SliderTrack> </SliderTrack>
<Tooltip <Tooltip hasArrow label={sliderValue} placement="bottom" gutter={10}>
hasArrow
label={sliderValue}
placement="bottom"
gutter={10}
>
<SliderThumb /> <SliderThumb />
</Tooltip> </Tooltip>
</Slider> </Slider>

View File

@@ -1,25 +1,25 @@
import { useNuiEvent } from "../../../hooks/useNuiEvent"; import { useNuiEvent } from '../../../hooks/useNuiEvent';
import { Box, Text, Flex, ScaleFade } from "@chakra-ui/react"; import { Box, Text, Flex, ScaleFade } from '@chakra-ui/react';
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react';
import { ContextMenuProps } from "../../../interfaces/context"; import { ContextMenuProps } from '../../../interfaces/context';
import Item from "./Item"; import Item from './Item';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { fetchNui } from "../../../utils/fetchNui"; import { fetchNui } from '../../../utils/fetchNui';
const openMenu = (id: string | undefined) => { const openMenu = (id: string | undefined) => {
fetchNui<ContextMenuProps>("openContext", id); fetchNui<ContextMenuProps>('openContext', id);
}; };
const ContextMenu: React.FC = () => { const ContextMenu: React.FC = () => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [contextMenu, setContextMenu] = useState<ContextMenuProps>({ const [contextMenu, setContextMenu] = useState<ContextMenuProps>({
title: "", title: '',
options: { "": { description: "", metadata: [] } }, options: { '': { description: '', metadata: [] } },
}); });
const closeContext = () => { const closeContext = () => {
setVisible(false); setVisible(false);
fetchNui("closeContext"); fetchNui('closeContext');
}; };
// Hides the context menu on ESC // Hides the context menu on ESC
@@ -27,17 +27,17 @@ const ContextMenu: React.FC = () => {
if (!visible) return; if (!visible) return;
const keyHandler = (e: KeyboardEvent) => { 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]); }, [visible]);
useNuiEvent("hideContext", () => setVisible(false)); useNuiEvent('hideContext', () => setVisible(false));
useNuiEvent<ContextMenuProps>("showContext", async (data) => { useNuiEvent<ContextMenuProps>('showContext', async (data) => {
if (visible) { if (visible) {
setVisible(false); setVisible(false);
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
@@ -47,13 +47,7 @@ const ContextMenu: React.FC = () => {
}); });
return ( return (
<Flex <Flex position="absolute" w="75%" h="80%" justifyContent="flex-end" alignItems="center">
position="absolute"
w="75%"
h="80%"
justifyContent="flex-end"
alignItems="center"
>
<ScaleFade in={visible} unmountOnExit> <ScaleFade in={visible} unmountOnExit>
<Box w="xs" h={580}> <Box w="xs" h={580}>
<Flex justifyContent="center" alignItems="center" mb={3}> <Flex justifyContent="center" alignItems="center" mb={3}>
@@ -68,7 +62,7 @@ const ContextMenu: React.FC = () => {
alignItems="center" alignItems="center"
marginRight={2} marginRight={2}
p={2} p={2}
_hover={{ bg: "gray.700" }} _hover={{ bg: 'gray.700' }}
transition="300ms" transition="300ms"
onClick={() => openMenu(contextMenu.menu)} onClick={() => openMenu(contextMenu.menu)}
> >
@@ -76,13 +70,7 @@ const ContextMenu: React.FC = () => {
</Flex> </Flex>
)} )}
<Box borderRadius="md" bg="gray.800" flex="1 85%"> <Box borderRadius="md" bg="gray.800" flex="1 85%">
<Text <Text fontFamily="Poppins" fontSize="md" p={2} textAlign="center" fontWeight="light">
fontFamily="Poppins"
fontSize="md"
p={2}
textAlign="center"
fontWeight="light"
>
{contextMenu.title} {contextMenu.title}
</Text> </Text>
</Box> </Box>
@@ -96,7 +84,7 @@ const ContextMenu: React.FC = () => {
alignItems="center" alignItems="center"
marginLeft={2} marginLeft={2}
p={2} p={2}
_hover={{ bg: "gray.700" }} _hover={{ bg: 'gray.700' }}
transition="300ms" transition="300ms"
onClick={() => closeContext()} onClick={() => closeContext()}
> >

View File

@@ -10,17 +10,17 @@ import {
Spacer, Spacer,
Image, Image,
HStack, HStack,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Option, ContextMenuProps } from "../../../interfaces/context"; import { Option, ContextMenuProps } from '../../../interfaces/context';
import { fetchNui } from "../../../utils/fetchNui"; import { fetchNui } from '../../../utils/fetchNui';
const openMenu = (id: string | undefined) => { const openMenu = (id: string | undefined) => {
fetchNui<ContextMenuProps>("openContext", id); fetchNui<ContextMenuProps>('openContext', id);
}; };
const clickContext = (id: string) => { const clickContext = (id: string) => {
fetchNui("clickContext", id); fetchNui('clickContext', id);
}; };
const Item: React.FC<{ const Item: React.FC<{
@@ -28,12 +28,7 @@ const Item: React.FC<{
}> = ({ option }) => { }> = ({ option }) => {
return ( return (
<> <>
<Popover <Popover placement="right-start" trigger="hover" eventListeners={{ scroll: true }} isLazy>
placement="right-start"
trigger="hover"
eventListeners={{ scroll: true }}
isLazy
>
<PopoverTrigger> <PopoverTrigger>
<Box <Box
bg="gray.800" bg="gray.800"
@@ -45,16 +40,12 @@ const Item: React.FC<{
fontFamily="Poppins" fontFamily="Poppins"
fontSize="md" fontSize="md"
transition="300ms" transition="300ms"
_hover={{ bg: "gray.700" }} _hover={{ bg: 'gray.700' }}
> >
<Flex <Flex
w="100%" w="100%"
alignItems="center" alignItems="center"
onClick={() => onClick={() => (option[1].menu ? openMenu(option[1].menu) : clickContext(option[0]))}
option[1].menu
? openMenu(option[1].menu)
: clickContext(option[0])
}
> >
{option[1]?.icon && ( {option[1]?.icon && (
<FontAwesomeIcon <FontAwesomeIcon
@@ -63,7 +54,7 @@ const Item: React.FC<{
fontSize={20} fontSize={20}
style={{ style={{
marginRight: 10, marginRight: 10,
justifySelf: "center", justifySelf: 'center',
color: option[1].iconColor, color: option[1].iconColor,
}} }}
/> />
@@ -83,12 +74,7 @@ const Item: React.FC<{
{(option[1].menu || option[1].arrow) && ( {(option[1].menu || option[1].arrow) && (
<> <>
<Spacer /> <Spacer />
<Box <Box alignSelf="center" justifySelf="center" mr={4} fontSize="xl">
alignSelf="center"
justifySelf="center"
mr={4}
fontSize="xl"
>
<FontAwesomeIcon icon="chevron-right" /> <FontAwesomeIcon icon="chevron-right" />
</Box> </Box>
</> </>
@@ -108,28 +94,19 @@ const Item: React.FC<{
<> <>
{option[1].image && <Image src={option[1].image} />} {option[1].image && <Image src={option[1].image} />}
{Array.isArray(option[1].metadata) ? ( {Array.isArray(option[1].metadata) ? (
option[1].metadata.map( option[1].metadata.map((metadata: string | { label: string; value: any }, index: number) => (
(
metadata: string | { label: string; value: any },
index: number
) => (
<Text key={`context-metadata-${index}`}> <Text key={`context-metadata-${index}`}>
{typeof metadata === "string" {typeof metadata === 'string' ? `${metadata}` : `${metadata.label}: ${metadata.value}`}
? `${metadata}`
: `${metadata.label}: ${metadata.value}`}
</Text> </Text>
) ))
)
) : ( ) : (
<> <>
{typeof option[1].metadata === "object" && {typeof option[1].metadata === 'object' &&
Object.entries(option[1].metadata).map( Object.entries(option[1].metadata).map((metadata: { [key: string]: any }, index) => (
(metadata: { [key: string]: any }, index) => (
<Text key={`context-metadata-${index}`}> <Text key={`context-metadata-${index}`}>
{metadata[0]}: {metadata[1]} {metadata[0]}: {metadata[1]}
</Text> </Text>
) ))}
)}
</> </>
)} )}
</> </>

View File

@@ -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 }) => { const Header: React.FC<{ title: string }> = ({ title }) => {
return ( return (

View File

@@ -1,7 +1,7 @@
import { Box, Flex, Stack, Spacer, Text, IconProps } from "@chakra-ui/react"; import { Box, Flex, Stack, Spacer, Text, IconProps } from '@chakra-ui/react';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { forwardRef } from "react"; import { forwardRef } from 'react';
import type { MenuItem } from "./index"; import type { MenuItem } from './index';
interface Props { interface Props {
item: MenuItem; item: MenuItem;
@@ -19,7 +19,7 @@ const ListItem = forwardRef<Array<HTMLDivElement | null>, Props>(({ item, index,
p={2} p={2}
height="60px" height="60px"
key={`item-${index}`} key={`item-${index}`}
_focus={{ bg: "#373A40", outline: "none" }} _focus={{ bg: '#373A40', outline: 'none' }}
ref={(element) => { ref={(element) => {
if (ref) if (ref)
// @ts-ignore i cba // @ts-ignore i cba

View File

@@ -1,13 +1,13 @@
import { Box, Stack, Tooltip } from "@chakra-ui/react"; import { Box, Stack, Tooltip } from '@chakra-ui/react';
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from 'react';
import { useNuiEvent } from "../../../hooks/useNuiEvent"; import { useNuiEvent } from '../../../hooks/useNuiEvent';
import ListItem from "./ListItem"; import ListItem from './ListItem';
import Header from "./Header"; import Header from './Header';
import FocusTrap from "focus-trap-react"; import FocusTrap from 'focus-trap-react';
import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { fetchNui } from "../../../utils/fetchNui"; import { fetchNui } from '../../../utils/fetchNui';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React from "react"; import React from 'react';
export interface MenuItem { export interface MenuItem {
label: string; label: string;
@@ -19,15 +19,15 @@ export interface MenuItem {
} }
export interface MenuSettings { export interface MenuSettings {
position?: "top-left" | "top-right" | "bottom-left" | "bottom-right"; position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
title: string; title: string;
items: Array<MenuItem>; items: Array<MenuItem>;
} }
const ListMenu: React.FC = () => { const ListMenu: React.FC = () => {
const [menu, setMenu] = useState<MenuSettings>({ const [menu, setMenu] = useState<MenuSettings>({
position: "top-left", position: 'top-left',
title: "", title: '',
items: [], items: [],
}); });
const [selected, setSelected] = useState(0); const [selected, setSelected] = useState(0);
@@ -37,53 +37,47 @@ const ListMenu: React.FC = () => {
const closeMenu = (ignoreFetch?: boolean) => { const closeMenu = (ignoreFetch?: boolean) => {
setVisible(false); setVisible(false);
if (!ignoreFetch) fetchNui("closeMenu"); if (!ignoreFetch) fetchNui('closeMenu');
}; };
const moveMenu = (e: React.KeyboardEvent<HTMLDivElement>) => { const moveMenu = (e: React.KeyboardEvent<HTMLDivElement>) => {
switch (e.code) { switch (e.code) {
case "ArrowDown": case 'ArrowDown':
setSelected((selected) => { setSelected((selected) => {
if (selected >= menu.items.length - 1) return (selected = 0); if (selected >= menu.items.length - 1) return (selected = 0);
return selected + 1; return selected + 1;
}); });
break; break;
case "ArrowUp": case 'ArrowUp':
setSelected((selected) => { setSelected((selected) => {
if (selected <= 0) return (selected = menu.items.length - 1); if (selected <= 0) return (selected = menu.items.length - 1);
return selected - 1; return selected - 1;
}); });
break; break;
case "ArrowRight": case 'ArrowRight':
if (!Array.isArray(menu.items[selected].values)) return; if (!Array.isArray(menu.items[selected].values)) return;
setIndexStates({ setIndexStates({
...indexStates, ...indexStates,
[selected]: [selected]:
indexStates[selected] + 1 <= indexStates[selected] + 1 <= menu.items[selected].values?.length! - 1
menu.items[selected].values?.length! - 1
? indexStates[selected] + 1 ? indexStates[selected] + 1
: (indexStates[selected] = 0), : (indexStates[selected] = 0),
}); });
break; break;
case "ArrowLeft": case 'ArrowLeft':
if (!Array.isArray(menu.items[selected].values)) return; if (!Array.isArray(menu.items[selected].values)) return;
setIndexStates({ setIndexStates({
...indexStates, ...indexStates,
[selected]: [selected]:
indexStates[selected] - 1 >= 0 indexStates[selected] - 1 >= 0
? indexStates[selected] - 1 ? indexStates[selected] - 1
: (indexStates[selected] = : (indexStates[selected] = menu.items[selected].values?.length! - 1),
menu.items[selected].values?.length! - 1),
}); });
break; break;
case "Enter": case 'Enter':
if (!menu.items[selected]) return; if (!menu.items[selected]) return;
fetchNui("confirmSelected", [selected, indexStates[selected]]).catch(); fetchNui('confirmSelected', [selected, indexStates[selected]]).catch();
if ( if (menu.items[selected].close === undefined || menu.items[selected].close) setVisible(false);
menu.items[selected].close === undefined ||
menu.items[selected].close
)
setVisible(false);
break; break;
} }
}; };
@@ -91,7 +85,7 @@ const ListMenu: React.FC = () => {
useEffect(() => { useEffect(() => {
if (!menu.items[selected]?.values) return; if (!menu.items[selected]?.values) return;
const timer = setTimeout(() => { const timer = setTimeout(() => {
fetchNui("changeIndex", [selected, indexStates[selected]]).catch(); fetchNui('changeIndex', [selected, indexStates[selected]]).catch();
}, 100); }, 100);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [indexStates]); }, [indexStates]);
@@ -99,13 +93,13 @@ const ListMenu: React.FC = () => {
useEffect(() => { useEffect(() => {
if (!menu.items[selected]) return; if (!menu.items[selected]) return;
listRefs.current[selected]?.scrollIntoView({ listRefs.current[selected]?.scrollIntoView({
block: "nearest", block: 'nearest',
inline: "start", inline: 'start',
}); });
listRefs.current[selected]?.focus({ preventScroll: true }); listRefs.current[selected]?.focus({ preventScroll: true });
// debounces the callback to avoid spam // debounces the callback to avoid spam
const timer = setTimeout(() => { const timer = setTimeout(() => {
fetchNui("changeSelected", [selected, indexStates[selected]]).catch(); fetchNui('changeSelected', [selected, indexStates[selected]]).catch();
}, 100); }, 100);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [selected, menu]); }, [selected, menu]);
@@ -114,26 +108,25 @@ const ListMenu: React.FC = () => {
if (!visible) return; if (!visible) return;
const keyHandler = (e: KeyboardEvent) => { 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]); }, [visible]);
useNuiEvent("closeMenu", () => closeMenu(true)); useNuiEvent('closeMenu', () => closeMenu(true));
useNuiEvent("setMenu", (data: MenuSettings) => { useNuiEvent('setMenu', (data: MenuSettings) => {
setSelected(0); setSelected(0);
if (!data.position) data.position = "top-left"; if (!data.position) data.position = 'top-left';
listRefs.current = []; listRefs.current = [];
setMenu(data); setMenu(data);
setVisible(true); setVisible(true);
let arrayIndexes: { [key: number]: number } = {}; let arrayIndexes: { [key: number]: number } = {};
for (let i = 0; i < data.items.length; i++) { for (let i = 0; i < data.items.length; i++) {
if (Array.isArray(data.items[i].values)) if (Array.isArray(data.items[i].values)) arrayIndexes[i] = (data.items[i].defaultIndex || 1) - 1;
arrayIndexes[i] = (data.items[i].defaultIndex || 1) - 1;
} }
setIndexStates(arrayIndexes); setIndexStates(arrayIndexes);
listRefs.current[0]?.focus(); listRefs.current[0]?.focus();
@@ -154,39 +147,13 @@ const ListMenu: React.FC = () => {
<Box <Box
position="absolute" position="absolute"
pointerEvents="none" pointerEvents="none"
mt={ mt={menu.position === 'top-left' || menu.position === 'top-right' ? 5 : 0}
menu.position === "top-left" || menu.position === "top-right" ml={menu.position === 'top-left' || menu.position === 'bottom-left' ? 5 : 0}
? 5 mr={menu.position === 'top-right' || menu.position === 'bottom-right' ? 5 : 0}
: 0 mb={menu.position === 'bottom-left' || menu.position === 'bottom-right' ? 5 : 0}
} right={menu.position === 'top-right' || menu.position === 'bottom-right' ? 1 : undefined}
ml={ left={menu.position === 'bottom-left' ? 1 : undefined}
menu.position === "top-left" || menu.position === "bottom-left" bottom={menu.position === 'bottom-left' || menu.position === 'bottom-right' ? 1 : undefined}
? 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} /> <Header title={menu.title} />
<Box <Box
@@ -194,11 +161,7 @@ const ListMenu: React.FC = () => {
height="fit-content" height="fit-content"
maxHeight={415} maxHeight={415}
overflow="hidden" overflow="hidden"
borderRadius={ borderRadius={menu.items.length <= 6 || selected === menu.items.length - 1 ? 'md' : undefined}
menu.items.length <= 6 || selected === menu.items.length - 1
? "md"
: undefined
}
bg="#141517" bg="#141517"
fontFamily="Nunito" fontFamily="Nunito"
borderTopLeftRadius="none" borderTopLeftRadius="none"
@@ -210,12 +173,7 @@ const ListMenu: React.FC = () => {
{menu.items.map((item, index) => ( {menu.items.map((item, index) => (
<React.Fragment key={`menu-item-${index}`}> <React.Fragment key={`menu-item-${index}`}>
{item.label && ( {item.label && (
<ListItem <ListItem index={index} item={item} scrollIndex={indexStates[index]} ref={listRefs} />
index={index}
item={item}
scrollIndex={indexStates[index]}
ref={listRefs}
/>
)} )}
</React.Fragment> </React.Fragment>
))} ))}
@@ -223,18 +181,8 @@ const ListMenu: React.FC = () => {
</FocusTrap> </FocusTrap>
</Box> </Box>
{menu.items.length > 6 && selected !== menu.items.length - 1 && ( {menu.items.length > 6 && selected !== menu.items.length - 1 && (
<Box <Box bg="#141517" textAlign="center" borderBottomLeftRadius="md" borderBottomRightRadius="md" height={25}>
bg="#141517" <FontAwesomeIcon icon="chevron-down" color="#909296" fontSize={20} />
textAlign="center"
borderBottomLeftRadius="md"
borderBottomRightRadius="md"
height={25}
>
<FontAwesomeIcon
icon="chevron-down"
color="#909296"
fontSize={20}
/>
</Box> </Box>
)} )}
</Box> </Box>

View File

@@ -1,20 +1,14 @@
import { import { useToast, type ToastPositionWithLogical, Box, HStack, Text } from '@chakra-ui/react';
useToast, import { useNuiEvent } from '../../hooks/useNuiEvent';
type ToastPositionWithLogical, import { IconProp } from '@fortawesome/fontawesome-svg-core';
Box, import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
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 { export interface NotificationProps {
title?: string; title?: string;
description?: string; description?: string;
duration?: number; duration?: number;
position?: ToastPositionWithLogical; position?: ToastPositionWithLogical;
status?: "info" | "warning" | "success" | "error"; status?: 'info' | 'warning' | 'success' | 'error';
id?: number; id?: number;
} }
@@ -33,31 +27,20 @@ export interface CustomNotificationProps {
const Notifications: React.FC = () => { const Notifications: React.FC = () => {
const toast = useToast(); const toast = useToast();
useNuiEvent<CustomNotificationProps>("customNotify", (data) => { useNuiEvent<CustomNotificationProps>('customNotify', (data) => {
if (!data.title && !data.description) return; if (!data.title && !data.description) return;
if (data.id && toast.isActive(data.id)) return; if (data.id && toast.isActive(data.id)) return;
if (!data.icon) { if (!data.icon) {
data.icon = data.icon = data.type === 'error' ? 'circle-xmark' : data.type === 'success' ? 'circle-check' : 'circle-info';
data.type === "error"
? "circle-xmark"
: data.type === "success"
? "circle-check"
: "circle-info";
} }
const id = data.id; const id = data.id;
toast({ toast({
id, id,
duration: data.duration || 3000, duration: data.duration || 3000,
position: data.position || "top-right", position: data.position || 'top-right',
render: () => ( render: () => (
<Box <Box className={`toast-${data.type || 'inform'}`} style={data.style} p={2} borderRadius="sm" boxShadow="md">
className={`toast-${data.type || "inform"}`}
style={data.style}
p={2}
borderRadius="sm"
boxShadow="md"
>
<HStack spacing={0}> <HStack spacing={0}>
{data.icon && ( {data.icon && (
<FontAwesomeIcon <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.title && !data.description) return;
if (data.id && toast.isActive(data.id)) return; if (data.id && toast.isActive(data.id)) return;
const id = data.id; const id = data.id;
@@ -87,7 +70,7 @@ const Notifications: React.FC = () => {
title: data.title, title: data.title,
description: data.description, description: data.description,
duration: data.duration || 4000, duration: data.duration || 4000,
position: data.position || "top-right", position: data.position || 'top-right',
status: data.status, status: data.status,
}); });
}); });

View File

@@ -1,31 +1,26 @@
import React from "react"; import React from 'react';
import { import { CircularProgress, CircularProgressLabel, Flex, Text } from '@chakra-ui/react';
CircularProgress, import { useNuiEvent } from '../../hooks/useNuiEvent';
CircularProgressLabel, import { fetchNui } from '../../utils/fetchNui';
Flex,
Text,
} from "@chakra-ui/react";
import { useNuiEvent } from "../../hooks/useNuiEvent";
import { fetchNui } from "../../utils/fetchNui";
export interface CircleProgressbarProps { export interface CircleProgressbarProps {
label?: string; label?: string;
duration: number; duration: number;
position?: "middle" | "bottom"; position?: 'middle' | 'bottom';
percent?: boolean; percent?: boolean;
} }
const CircleProgressbar: React.FC = () => { const CircleProgressbar: React.FC = () => {
const [visible, setVisible] = React.useState(false); const [visible, setVisible] = React.useState(false);
const [progressDuration, setProgressDuration] = React.useState(0); 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 [value, setValue] = React.useState(0);
const [label, setLabel] = React.useState(""); const [label, setLabel] = React.useState('');
const [cancelled, setCancelled] = React.useState(false); const [cancelled, setCancelled] = React.useState(false);
const progressComplete = () => { const progressComplete = () => {
setVisible(false); setVisible(false);
fetchNui("progressComplete"); fetchNui('progressComplete');
}; };
const progressCancel = () => { const progressCancel = () => {
@@ -34,16 +29,16 @@ const CircleProgressbar: React.FC = () => {
setVisible(false); setVisible(false);
}; };
useNuiEvent("progressCancel", progressCancel); useNuiEvent('progressCancel', progressCancel);
useNuiEvent<CircleProgressbarProps>("circleProgress", (data) => { useNuiEvent<CircleProgressbarProps>('circleProgress', (data) => {
if (visible) return; if (visible) return;
setCancelled(false); setCancelled(false);
setVisible(true); setVisible(true);
setValue(0); setValue(0);
setLabel(data.label || ""); setLabel(data.label || '');
setProgressDuration(data.duration); setProgressDuration(data.duration);
setPosition(data.position || "middle"); setPosition(data.position || 'middle');
const onePercent = data.duration * 0.01; const onePercent = data.duration * 0.01;
const updateProgress = setInterval(() => { const updateProgress = setInterval(() => {
setValue((previousValue) => { setValue((previousValue) => {
@@ -56,7 +51,7 @@ const CircleProgressbar: React.FC = () => {
return ( return (
<Flex <Flex
h={position === "middle" ? "100%" : "20%"} h={position === 'middle' ? '100%' : '20%'}
w="100%" w="100%"
position="absolute" position="absolute"
bottom="0" bottom="0"
@@ -71,29 +66,27 @@ const CircleProgressbar: React.FC = () => {
trackColor="rgba(0, 0, 0, 0.6)" trackColor="rgba(0, 0, 0, 0.6)"
onAnimationEnd={progressComplete} onAnimationEnd={progressComplete}
thickness={6} thickness={6}
color={cancelled ? "rgb(198, 40, 40)" : "white"} color={cancelled ? 'rgb(198, 40, 40)' : 'white'}
sx={ sx={
!cancelled !cancelled
? { ? {
".chakra-progress__indicator": { '.chakra-progress__indicator': {
transition: "none !important", transition: 'none !important',
animation: "progress linear forwards !important", animation: 'progress linear forwards !important',
animationDuration: `${progressDuration}ms !important`, animationDuration: `${progressDuration}ms !important`,
opacity: "1 !important", opacity: '1 !important',
}, },
} }
: { : {
// Currently unused // Currently unused
".chakra-progress__indicator": { '.chakra-progress__indicator': {
transition: "none !important", transition: 'none !important',
strokeDasharray: "264, 0 !important", // sets circle to full strokeDasharray: '264, 0 !important', // sets circle to full
}, },
} }
} }
> >
<CircularProgressLabel fontFamily="Fira Mono"> <CircularProgressLabel fontFamily="Fira Mono">{value}%</CircularProgressLabel>
{value}%
</CircularProgressLabel>
</CircularProgress> </CircularProgress>
<Text fontFamily="Inter" isTruncated fontSize={18} fontWeight="light"> <Text fontFamily="Inter" isTruncated fontSize={18} fontWeight="light">
{label} {label}

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import { Text, Flex, Box } from "@chakra-ui/react"; import { Text, Flex, Box } from '@chakra-ui/react';
import { useNuiEvent } from "../../hooks/useNuiEvent"; import { useNuiEvent } from '../../hooks/useNuiEvent';
import { fetchNui } from "../../utils/fetchNui"; import { fetchNui } from '../../utils/fetchNui';
export interface ProgressbarProps { export interface ProgressbarProps {
label: string; label: string;
@@ -10,13 +10,13 @@ export interface ProgressbarProps {
const Progressbar: React.FC = () => { const Progressbar: React.FC = () => {
const [visible, setVisible] = React.useState(false); const [visible, setVisible] = React.useState(false);
const [label, setLabel] = React.useState(""); const [label, setLabel] = React.useState('');
const [duration, setDuration] = React.useState(0); const [duration, setDuration] = React.useState(0);
const [cancelled, setCancelled] = React.useState(false); const [cancelled, setCancelled] = React.useState(false);
const progressComplete = () => { const progressComplete = () => {
setVisible(false); setVisible(false);
fetchNui("progressComplete"); fetchNui('progressComplete');
}; };
const progressCancel = () => { const progressCancel = () => {
@@ -24,9 +24,9 @@ const Progressbar: React.FC = () => {
setVisible(false); setVisible(false);
}; };
useNuiEvent("progressCancel", progressCancel); useNuiEvent('progressCancel', progressCancel);
useNuiEvent<ProgressbarProps>("progress", (data) => { useNuiEvent<ProgressbarProps>('progress', (data) => {
setCancelled(false); setCancelled(false);
setVisible(true); setVisible(true);
setLabel(data.label); setLabel(data.label);
@@ -34,14 +34,7 @@ const Progressbar: React.FC = () => {
}); });
return ( return (
<Flex <Flex h="30%" w="100%" position="absolute" bottom="0" justifyContent="center" alignItems="center">
h="30%"
w="100%"
position="absolute"
bottom="0"
justifyContent="center"
alignItems="center"
>
<Box width={350}> <Box width={350}>
{visible && ( {visible && (
<Box <Box
@@ -58,16 +51,16 @@ const Progressbar: React.FC = () => {
sx={ sx={
!cancelled !cancelled
? { ? {
width: "0%", width: '0%',
backgroundColor: "green.400", backgroundColor: 'green.400',
animation: "progress-bar linear", animation: 'progress-bar linear',
animationDuration: `${duration}ms`, animationDuration: `${duration}ms`,
} }
: { : {
// Currently unused // Currently unused
width: "100%", width: '100%',
animationPlayState: "paused", animationPlayState: 'paused',
backgroundColor: "rgb(198, 40, 40)", backgroundColor: 'rgb(198, 40, 40)',
} }
} }
/> />

View File

@@ -1,6 +1,6 @@
import { HStack, Text, Select, Spacer } from "@chakra-ui/react"; import { HStack, Text, Select, Spacer } from '@chakra-ui/react';
import { useLocales } from "../../../providers/LocaleProvider"; import { useLocales } from '../../../providers/LocaleProvider';
import { fetchNui } from "../../../utils/fetchNui"; import { fetchNui } from '../../../utils/fetchNui';
const LocaleSetting: React.FC<{ const LocaleSetting: React.FC<{
languages: string[]; languages: string[];
@@ -11,18 +11,14 @@ const LocaleSetting: React.FC<{
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => { const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
props.setSelectValue(e.target.value); props.setSelectValue(e.target.value);
fetchNui("getLocale", e.target.value); fetchNui('getLocale', e.target.value);
}; };
return ( return (
<HStack spacing={5}> <HStack spacing={5}>
<Text>{locale.ui.settings.language}</Text> <Text>{locale.ui.settings.language}</Text>
<Spacer /> <Spacer />
<Select <Select onChange={(e) => handleChange(e)} value={props.selectValue} w={150}>
onChange={(e) => handleChange(e)}
value={props.selectValue}
w={150}
>
{props.languages.map((language) => ( {props.languages.map((language) => (
<option key={language} value={language}> <option key={language} value={language}>
{language} {language}

View File

@@ -7,26 +7,26 @@ import {
ModalHeader, ModalHeader,
ModalOverlay, ModalOverlay,
useDisclosure, useDisclosure,
} from "@chakra-ui/react"; } from '@chakra-ui/react';
import LocaleSetting from "./components/locale"; import LocaleSetting from './components/locale';
import { useState } from "react"; import { useState } from 'react';
import { useNuiEvent } from "../../hooks/useNuiEvent"; import { useNuiEvent } from '../../hooks/useNuiEvent';
import { fetchNui } from "../../utils/fetchNui"; import { fetchNui } from '../../utils/fetchNui';
import { useLocales } from "../../providers/LocaleProvider"; import { useLocales } from '../../providers/LocaleProvider';
const Settings: React.FC = () => { const Settings: React.FC = () => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const { locale } = useLocales(); const { locale } = useLocales();
const [languages, setLanguages] = useState([""]); const [languages, setLanguages] = useState(['']);
const [selectValue, setSelectValue] = useState(""); const [selectValue, setSelectValue] = useState('');
const closeSettings = () => { const closeSettings = () => {
onClose(); onClose();
fetchNui("closeSettings"); fetchNui('closeSettings');
}; };
useNuiEvent("loadLocales", (data) => setLanguages(data)); useNuiEvent('loadLocales', (data) => setLanguages(data));
useNuiEvent("openSettings", (data: string) => { useNuiEvent('openSettings', (data: string) => {
onOpen(); onOpen();
setSelectValue(data); setSelectValue(data);
}); });
@@ -46,11 +46,7 @@ const Settings: React.FC = () => {
<ModalContent> <ModalContent>
<ModalHeader>{locale.ui.settings.title}</ModalHeader> <ModalHeader>{locale.ui.settings.title}</ModalHeader>
<ModalBody> <ModalBody>
<LocaleSetting <LocaleSetting languages={languages} selectValue={selectValue} setSelectValue={setSelectValue} />
languages={languages}
selectValue={selectValue}
setSelectValue={setSelectValue}
/>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button colorScheme="blue" onClick={closeSettings}> <Button colorScheme="blue" onClick={closeSettings}>

View File

@@ -1,13 +1,13 @@
import React from "react"; import React from 'react';
import { useNuiEvent } from "../../hooks/useNuiEvent"; import { useNuiEvent } from '../../hooks/useNuiEvent';
import { Box, Flex, ScaleFade } from "@chakra-ui/react"; import { Box, Flex, ScaleFade } from '@chakra-ui/react';
import ReactMarkdown from "react-markdown"; import ReactMarkdown from 'react-markdown';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { IconProp } from '@fortawesome/fontawesome-svg-core';
export interface TextUiProps { export interface TextUiProps {
text: string; text: string;
position?: "right-center" | "left-center" | "top-center"; position?: 'right-center' | 'left-center' | 'top-center';
icon?: IconProp; icon?: IconProp;
iconColor?: string; iconColor?: string;
style?: React.CSSProperties; style?: React.CSSProperties;
@@ -15,18 +15,18 @@ export interface TextUiProps {
const TextUI: React.FC = () => { const TextUI: React.FC = () => {
const [data, setData] = React.useState<TextUiProps>({ const [data, setData] = React.useState<TextUiProps>({
text: "", text: '',
position: "right-center", position: 'right-center',
}); });
const [visible, setVisible] = React.useState(false); const [visible, setVisible] = React.useState(false);
useNuiEvent<TextUiProps>("textUi", (data) => { useNuiEvent<TextUiProps>('textUi', (data) => {
if (!data.position) data.position = "right-center"; // Default right position if (!data.position) data.position = 'right-center'; // Default right position
setData(data); setData(data);
setVisible(true); setVisible(true);
}); });
useNuiEvent("textUiHide", () => setVisible(false)); useNuiEvent('textUiHide', () => setVisible(false));
return ( return (
<Flex <Flex
@@ -34,13 +34,9 @@ const TextUI: React.FC = () => {
h="100%" h="100%"
p={3} p={3}
position="absolute" position="absolute"
alignItems={data.position === "top-center" ? "baseline" : "center"} alignItems={data.position === 'top-center' ? 'baseline' : 'center'}
justifyContent={ justifyContent={
data.position === "right-center" data.position === 'right-center' ? 'flex-end' : data.position === 'left-center' ? 'flex-start' : 'center'
? "flex-end"
: data.position === "left-center"
? "flex-start"
: "center"
} }
> >
<ScaleFade in={visible} unmountOnExit> <ScaleFade in={visible} unmountOnExit>

View File

@@ -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 [keyPressed, setKeyPressed] = React.useState(false);
const downHandler = React.useCallback( const downHandler = React.useCallback(
@@ -22,12 +22,12 @@ export const useKeyPress = (targetKey: KeyboardEvent["key"]) => {
); );
React.useEffect(() => { React.useEffect(() => {
window.addEventListener("keydown", downHandler); window.addEventListener('keydown', downHandler);
window.addEventListener("keyup", upHandler); window.addEventListener('keyup', upHandler);
return () => { return () => {
window.removeEventListener("keydown", downHandler); window.removeEventListener('keydown', downHandler);
window.removeEventListener("keyup", upHandler); window.removeEventListener('keyup', upHandler);
}; };
}, [downHandler, upHandler]); }, [downHandler, upHandler]);

View File

@@ -1,5 +1,5 @@
import {MutableRefObject, useEffect, useRef} from "react"; import { MutableRefObject, useEffect, useRef } from 'react';
import {noop} from "../utils/misc"; import { noop } from '../utils/misc';
interface NuiMessageData<T = unknown> { interface NuiMessageData<T = unknown> {
action: string; action: string;
@@ -20,10 +20,7 @@ type NuiHandlerSignature<T> = (data: T) => void;
* *
**/ **/
export const useNuiEvent = <T = any>( export const useNuiEvent = <T = any>(action: string, handler: (data: T) => void) => {
action: string,
handler: (data: T) => void
) => {
const savedHandler: MutableRefObject<NuiHandlerSignature<T>> = useRef(noop); const savedHandler: MutableRefObject<NuiHandlerSignature<T>> = useRef(noop);
// Make sure we handle for a reactive handler // 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 // Remove Event Listener on component cleanup
return () => window.removeEventListener("message", eventListener); return () => window.removeEventListener('message', eventListener);
}, [action]); }, [action]);
}; };

View File

@@ -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=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=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=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=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=Nunito:wght@300;400;500;600;700&display=swap');
body { body {
background: none !important; background: none !important;
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
"Droid Sans", "Helvetica Neue", sans-serif; 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
height: 100vh; height: 100vh;
@@ -21,7 +21,7 @@ body {
} }
code { 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 { @keyframes progress {

View File

@@ -1,4 +1,4 @@
import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { IconProp } from '@fortawesome/fontawesome-svg-core';
export interface Option { export interface Option {
menu?: string; menu?: string;
@@ -8,10 +8,7 @@ export interface Option {
image?: string; image?: string;
icon?: IconProp; icon?: IconProp;
iconColor?: string; iconColor?: string;
metadata?: metadata?: string[] | { [key: string]: any } | { label: string; value: any }[];
| string[]
| { [key: string]: any }
| { label: string; value: any }[];
event?: string; event?: string;
serverEvent?: string; serverEvent?: string;
args?: any; args?: any;

View File

@@ -1,7 +1,7 @@
import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { IconProp } from '@fortawesome/fontawesome-svg-core';
export interface IInput { export interface IInput {
type: "input", type: 'input';
label: string; label: string;
placeholder?: string; placeholder?: string;
default?: string; default?: string;
@@ -10,27 +10,29 @@ export interface IInput {
} }
export interface ICheckbox { export interface ICheckbox {
type: "checkbox", type: 'checkbox';
label: string; label: string;
checked?: boolean; checked?: boolean;
} }
export interface ISelect { export interface ISelect {
type: "select", type: 'select';
label: string; label: string;
default?: string; default?: string;
options?: { value: string; label?: string }[]; options?: { value: string; label?: string }[];
} }
export interface INumber { export interface INumber {
type: "number", type: 'number';
label: string; label: string;
placeholder?: string; placeholder?: string;
default?: number; default?: number;
min?: number;
max?: number;
} }
export interface ISlider { export interface ISlider {
type: "slider", type: 'slider';
label: string; label: string;
default?: number; default?: number;
min?: number; min?: number;

View File

@@ -1,33 +1,33 @@
import React from "react"; import React from 'react';
import ReactDOM from "react-dom"; import ReactDOM from 'react-dom';
import "./index.css"; import './index.css';
import App from "./App"; import App from './App';
import { VisibilityProvider } from "./providers/VisibilityProvider"; import { VisibilityProvider } from './providers/VisibilityProvider';
import { ChakraProvider } from "@chakra-ui/react"; import { ChakraProvider } from '@chakra-ui/react';
import { theme } from "./theme"; import { theme } from './theme';
import { debugData } from "./utils/debugData"; import { debugData } from './utils/debugData';
import { fas } from "@fortawesome/free-solid-svg-icons"; import { fas } from '@fortawesome/free-solid-svg-icons';
import { far } from "@fortawesome/free-regular-svg-icons"; import { far } from '@fortawesome/free-regular-svg-icons';
import { fab } from "@fortawesome/free-brands-svg-icons"; import { fab } from '@fortawesome/free-brands-svg-icons';
import { library } from "@fortawesome/fontawesome-svg-core"; import { library } from '@fortawesome/fontawesome-svg-core';
import { isEnvBrowser } from "./utils/misc"; import { isEnvBrowser } from './utils/misc';
import LocaleProvider from "./providers/LocaleProvider"; import LocaleProvider from './providers/LocaleProvider';
library.add(fas, far, fab); library.add(fas, far, fab);
if (isEnvBrowser()) { if (isEnvBrowser()) {
const root = document.getElementById("root"); const root = document.getElementById('root');
// https://i.imgur.com/iPTAdYV.png - Night time img // https://i.imgur.com/iPTAdYV.png - Night time img
root!.style.backgroundImage = 'url("https://i.imgur.com/3pzRj9n.png")'; root!.style.backgroundImage = 'url("https://i.imgur.com/3pzRj9n.png")';
root!.style.backgroundSize = "cover"; root!.style.backgroundSize = 'cover';
root!.style.backgroundRepeat = "no-repeat"; root!.style.backgroundRepeat = 'no-repeat';
root!.style.backgroundPosition = "center"; root!.style.backgroundPosition = 'center';
} }
debugData([ debugData([
{ {
action: "setVisible", action: 'setVisible',
data: true, data: true,
}, },
]); ]);
@@ -42,5 +42,5 @@ ReactDOM.render(
</VisibilityProvider> </VisibilityProvider>
</LocaleProvider> </LocaleProvider>
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById('root')
); );

View File

@@ -1,40 +1,26 @@
import { Context, createContext, useContext, useState } from "react"; import { Context, createContext, useContext, useState } from 'react';
import { useNuiEvent } from "../hooks/useNuiEvent"; import { useNuiEvent } from '../hooks/useNuiEvent';
import { debugData } from "../utils/debugData"; import { debugData } from '../utils/debugData';
debugData([ debugData([
{ {
action: "loadLocales", action: 'loadLocales',
data: [ data: ['en', 'fr', 'de', 'it', 'es', 'pt-BR', 'pl', 'ru', 'ko', 'zh-TW', 'ja', 'es-MX', 'zh-CN'],
"en",
"fr",
"de",
"it",
"es",
"pt-BR",
"pl",
"ru",
"ko",
"zh-TW",
"ja",
"es-MX",
"zh-CN",
],
}, },
]); ]);
debugData([ debugData([
{ {
action: "setLocale", action: 'setLocale',
data: { data: {
language: "English", language: 'English',
ui: { ui: {
settings: { settings: {
title: "Settings", title: 'Settings',
language: "Language", language: 'Language',
}, },
close: "Close", close: 'Close',
confirm: "Confirm", confirm: 'Confirm',
}, },
}, },
}, },
@@ -62,28 +48,23 @@ const LocaleCtx = createContext<LocaleContextValue | null>(null);
const LocaleProvider: React.FC = ({ children }) => { const LocaleProvider: React.FC = ({ children }) => {
const [locale, setLocale] = useState<Locale>({ const [locale, setLocale] = useState<Locale>({
language: "", language: '',
ui: { ui: {
settings: { settings: {
title: "", title: '',
language: "", language: '',
}, },
cancel: "", cancel: '',
close: "", close: '',
confirm: "", confirm: '',
}, },
}); });
useNuiEvent("setLocale", async (data: Locale) => setLocale(data)); useNuiEvent('setLocale', async (data: Locale) => setLocale(data));
return ( return <LocaleCtx.Provider value={{ locale, setLocale }}>{children}</LocaleCtx.Provider>;
<LocaleCtx.Provider value={{ locale, setLocale }}>
{children}
</LocaleCtx.Provider>
);
}; };
export default LocaleProvider; export default LocaleProvider;
export const useLocales = () => export const useLocales = () => useContext<LocaleContextValue>(LocaleCtx as Context<LocaleContextValue>);
useContext<LocaleContextValue>(LocaleCtx as Context<LocaleContextValue>);

View File

@@ -1,5 +1,5 @@
import React, { Context, createContext, useContext, useState } from "react"; import React, { Context, createContext, useContext, useState } from 'react';
import { useNuiEvent } from "../hooks/useNuiEvent"; import { useNuiEvent } from '../hooks/useNuiEvent';
const VisibilityCtx = createContext<VisibilityProviderValue | null>(null); const VisibilityCtx = createContext<VisibilityProviderValue | null>(null);
@@ -13,7 +13,7 @@ interface VisibilityProviderValue {
export const VisibilityProvider: React.FC = ({ children }) => { export const VisibilityProvider: React.FC = ({ children }) => {
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true);
useNuiEvent<boolean>("setVisible", setVisible); useNuiEvent<boolean>('setVisible', setVisible);
return ( return (
<VisibilityCtx.Provider <VisibilityCtx.Provider
@@ -22,16 +22,10 @@ export const VisibilityProvider: React.FC = ({ children }) => {
setVisible, setVisible,
}} }}
> >
<div <div style={{ visibility: visible ? 'visible' : 'hidden', height: '100%' }}>{children}</div>
style={{ visibility: visible ? "visible" : "hidden", height: "100%" }}
>
{children}
</div>
</VisibilityCtx.Provider> </VisibilityCtx.Provider>
); );
}; };
export const useVisibility = () => export const useVisibility = () =>
useContext<VisibilityProviderValue>( useContext<VisibilityProviderValue>(VisibilityCtx as Context<VisibilityProviderValue>);
VisibilityCtx as Context<VisibilityProviderValue>
);

View File

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

View File

@@ -1,4 +1,4 @@
import {isEnvBrowser} from "./misc"; import { isEnvBrowser } from './misc';
interface DebugEvent<T = any> { interface DebugEvent<T = any> {
action: string; action: string;
@@ -13,11 +13,11 @@ interface DebugEvent<T = any> {
* @param timer - How long until it should trigger (ms) * @param timer - How long until it should trigger (ms)
*/ */
export const debugData = <P>(events: DebugEvent<P>[], timer = 1000): void => { 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) { for (const event of events) {
setTimeout(() => { setTimeout(() => {
window.dispatchEvent( window.dispatchEvent(
new MessageEvent("message", { new MessageEvent('message', {
data: { data: {
action: event.action, action: event.action,
data: event.data, data: event.data,

View File

@@ -18,11 +18,13 @@ export async function fetchNui<T = any>(eventName: string, data?: any): Promise<
body: JSON.stringify(data), 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 resp = await fetch(`https://${resourceName}/${eventName}`, options);
const respFormatted = await resp.json() const respFormatted = await resp.json();
return respFormatted return respFormatted;
} }

View File

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