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",
"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

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 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 (
<>

View File

@@ -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,
},

View File

@@ -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' },
},
],
},

View File

@@ -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 },
],
},
},

View File

@@ -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'],
},
],
},

View File

@@ -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',
},
},
},

View File

@@ -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',
},
},
]);

View File

@@ -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,
},
]);
};

View File

@@ -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',
},
},
]);

View File

@@ -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();

View File

@@ -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>

View File

@@ -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}>

View File

@@ -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 (

View File

@@ -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 }}
/>

View File

@@ -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;

View File

@@ -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}

View File

@@ -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>

View File

@@ -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()}
>

View File

@@ -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>
))}
</>
)}
</>

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 }) => {
return (

View File

@@ -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

View File

@@ -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>

View File

@@ -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,
});
});

View File

@@ -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}

View File

@@ -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)',
}
}
/>

View File

@@ -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}

View File

@@ -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}>

View File

@@ -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>

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 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]);

View File

@@ -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]);
};

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=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 {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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')
);

View File

@@ -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>);

View File

@@ -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>);

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 = {
initialColorMode: 'dark',
useSystemColorMode: false
}
initialColorMode: 'dark',
useSystemColorMode: false,
};
export const theme = extendTheme({
config,
})
config,
});

View File

@@ -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,
@@ -27,4 +27,4 @@ export const debugData = <P>(events: DebugEvent<P>[], timer = 1000): void => {
}, timer);
}
}
};
};

View File

@@ -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;
}

View File

@@ -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 = () => {};

View File

@@ -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);
};