├── .gitignore ├── imports ├── inventory │ ├── qb │ │ └── server.lua │ ├── server.lua │ ├── ox │ │ └── server.lua │ └── esx │ │ └── server.lua ├── getCrosshairSave │ └── client.lua ├── exports │ └── shared.lua ├── mysql │ └── server.lua ├── emit │ ├── client.lua │ └── server.lua ├── framework │ ├── client.lua │ ├── server.lua │ ├── qb │ │ ├── client.lua │ │ └── server.lua │ └── esx │ │ ├── server.lua │ │ └── client.lua ├── json │ ├── client.lua │ └── server.lua ├── on │ ├── client.lua │ └── server.lua ├── waitFor │ └── shared.lua ├── player │ └── client.lua ├── exportsClass │ └── shared.lua ├── time │ └── server.lua ├── area │ └── client.lua ├── string │ └── shared.lua ├── class │ └── shared.lua ├── promise │ └── shared.lua ├── callback │ ├── server.lua │ └── client.lua ├── disableControls │ └── client.lua ├── table │ └── shared.lua ├── raycast │ └── client.lua ├── timeout │ └── shared.lua ├── version │ └── server.lua ├── playAnim │ └── client.lua ├── locale │ └── shared.lua ├── request │ └── client.lua ├── closest │ └── client.lua ├── require │ └── shared.lua ├── vehicle │ └── server.lua └── points │ └── client.lua ├── web ├── src │ ├── features │ │ ├── notify │ │ │ └── components │ │ │ │ ├── _title.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── _action.tsx │ │ │ │ └── _description.tsx │ │ ├── billing │ │ │ └── _components │ │ │ │ ├── index.ts │ │ │ │ ├── normal.tsx │ │ │ │ └── amende.tsx │ │ ├── resource │ │ │ └── components │ │ │ │ ├── index.ts │ │ │ │ ├── _switch.tsx │ │ │ │ ├── _input.tsx │ │ │ │ ├── _object_switch.tsx │ │ │ │ ├── _object_string.tsx │ │ │ │ ├── _array_switch.tsx │ │ │ │ ├── _buttons.tsx │ │ │ │ ├── _badge.tsx │ │ │ │ └── _canAdd.tsx │ │ ├── modal │ │ │ ├── components │ │ │ │ ├── custom │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── _checkbox.tsx │ │ │ │ │ ├── _password.tsx │ │ │ │ │ ├── _input.tsx │ │ │ │ │ ├── _slider.tsx │ │ │ │ │ ├── _colorpicker.tsx │ │ │ │ │ ├── _number.tsx │ │ │ │ │ ├── _select.tsx │ │ │ │ │ ├── _multiselect.tsx │ │ │ │ │ └── _dateInput.tsx │ │ │ │ ├── dateInput.tsx │ │ │ │ └── buttons.tsx │ │ │ └── ModalConfirm.tsx │ │ ├── tool │ │ │ └── ConvertUnix.tsx │ │ └── chat │ │ │ ├── Emoji.tsx │ │ │ └── Reactions.tsx │ ├── react-app-env.d.ts │ ├── typings │ │ ├── InputDate.ts │ │ ├── ConvertUnix.ts │ │ ├── config │ │ │ ├── modals.ts │ │ │ ├── emojipicker.ts │ │ │ └── notifications.ts │ │ ├── Dialog.ts │ │ ├── index.ts │ │ ├── Crosshair.tsx │ │ ├── Notification.ts │ │ └── Modal.ts │ ├── dev │ │ ├── config │ │ │ ├── index.ts │ │ │ ├── emojipicker.ts │ │ │ └── notifications.ts │ │ └── debug │ │ │ ├── copy.ts │ │ │ ├── crosshairTool.ts │ │ │ ├── action.ts │ │ │ ├── dialog.ts │ │ │ ├── modals │ │ │ ├── confirm.ts │ │ │ └── custom.ts │ │ │ └── billing.ts │ ├── theme │ │ └── index.ts │ ├── utils │ │ ├── index.ts │ │ ├── markdown.tsx │ │ ├── misc.ts │ │ ├── debugData.ts │ │ └── fetchNui.ts │ ├── setupTests.ts │ ├── animation │ │ ├── icones.ts │ │ ├── notifications.ts │ │ └── keyframes │ │ │ └── notifcation.ts │ ├── index.tsx │ ├── index.css │ ├── providers │ │ └── ConfigProvider.tsx │ ├── hooks │ │ └── useNuiEvent.ts │ └── App.tsx ├── .gitignore ├── tsconfig.json ├── craco.config.js ├── public │ └── index.html ├── README.md └── package.json ├── data ├── shared │ └── handlers.json ├── server │ └── webhook.json └── client │ └── marker.json ├── modules ├── main │ ├── index.lua │ └── client │ │ ├── resource.lua │ │ └── cache.lua ├── handlers │ ├── client │ │ ├── ace.lua │ │ ├── blips.lua │ │ ├── events.lua │ │ └── nui.lua │ ├── index.lua │ └── server │ │ ├── vehicles.lua │ │ ├── ace.lua │ │ └── webhook.lua ├── nui │ ├── index.lua │ ├── server │ │ └── notify.lua │ └── client │ │ ├── config.lua │ │ ├── crosshair.lua │ │ ├── action.lua │ │ └── modals.lua └── init.lua ├── config ├── shared │ └── handlers.lua ├── modules.lua ├── server │ └── webhook.lua └── client │ └── marker.lua ├── locales ├── en.json └── fr.json ├── version.json ├── .github └── dependabot.yml ├── fxmanifest.lua ├── README.md └── init.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | web/yarn.lock 3 | -------------------------------------------------------------------------------- /imports/inventory/qb/server.lua: -------------------------------------------------------------------------------- 1 | ---@todo -------------------------------------------------------------------------------- /web/src/features/notify/components/_title.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/shared/handlers.json: -------------------------------------------------------------------------------- 1 | { 2 | "blips": true 3 | } -------------------------------------------------------------------------------- /web/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/src/typings/InputDate.ts: -------------------------------------------------------------------------------- 1 | export type InputDateProps = { 2 | 3 | } -------------------------------------------------------------------------------- /web/src/dev/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './notifications'; 2 | export * from './emojipicker'; -------------------------------------------------------------------------------- /web/src/typings/ConvertUnix.ts: -------------------------------------------------------------------------------- 1 | export interface ConvertUnixProps { 2 | unix_time: number; 3 | format_date?: string; 4 | } -------------------------------------------------------------------------------- /web/src/features/billing/_components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './amende' 2 | export * from './item_service' 3 | export * from './normal' -------------------------------------------------------------------------------- /web/src/features/notify/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './_action'; 2 | //export * from './_title'; 3 | export * from './_description'; -------------------------------------------------------------------------------- /web/src/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { MantineThemeOverride } from '@mantine/core'; 2 | 3 | export const themeOverride: MantineThemeOverride = { 4 | fontFamily: 'Sarabun', 5 | } -------------------------------------------------------------------------------- /web/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './debugData' 2 | export * from './misc' 3 | export * from './fetchNui' 4 | export * from './markdown' 5 | export * from '../hooks/useNuiEvent'; -------------------------------------------------------------------------------- /modules/main/index.lua: -------------------------------------------------------------------------------- 1 | return { 2 | client = { 3 | 'cache', 4 | --'resource' 5 | }, 6 | 7 | -- server = { 8 | --'resource' 9 | -- } 10 | } -------------------------------------------------------------------------------- /config/shared/handlers.lua: -------------------------------------------------------------------------------- 1 | local active = true -- or nil 2 | 3 | if not active then return end 4 | local loadjson = require 'imports.json.server'.load 5 | return loadjson('data.shared.handlers') -------------------------------------------------------------------------------- /imports/getCrosshairSave/client.lua: -------------------------------------------------------------------------------- 1 | function supv.getCrosshairSave(cb) 2 | if type(cb) ~= 'function' then return end 3 | AddEventHandler('supv_core:crosshair:save', cb) 4 | end 5 | 6 | return supv.getCrosshairSave -------------------------------------------------------------------------------- /web/src/typings/config/modals.ts: -------------------------------------------------------------------------------- 1 | export interface ModalsProviderProps { 2 | container: { 3 | width: string | number; 4 | backgroundColor: string; 5 | backgroundImage: string; 6 | fontFamily: string; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /modules/handlers/client/ace.lua: -------------------------------------------------------------------------------- 1 | -- suvp.playerHasAce('command.supv_core') example 2 | ---@param command string 3 | ---@return unknown 4 | function supv.playerHasAce(command) 5 | return callback.sync('getPlayerAce', false, command) 6 | end -------------------------------------------------------------------------------- /locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "init": "supv_core init with success!", 3 | "need_update": "^3Update this resource %s\n^3your version : ^1%s ^7->^4 new version : ^2%s\n^3link : ^4%s", 4 | "error_update": "^1Impossible to check version of script" 5 | } -------------------------------------------------------------------------------- /web/src/dev/debug/copy.ts: -------------------------------------------------------------------------------- 1 | import { debugData } from "../../utils/debugData"; 2 | 3 | export const debugCopy = async () => { 4 | debugData([ 5 | { 6 | action: "supv_core:copy", 7 | data: "Hello World!", 8 | }, 9 | ]); 10 | }; -------------------------------------------------------------------------------- /modules/nui/index.lua: -------------------------------------------------------------------------------- 1 | return { 2 | client = { 3 | 'config', 4 | 'modals', 5 | 'notify', 6 | 'crosshair', 7 | 'billing', 8 | 'action', 9 | }, 10 | 11 | server = { 12 | 'notify', 13 | } 14 | } -------------------------------------------------------------------------------- /modules/nui/server/notify.lua: -------------------------------------------------------------------------------- 1 | local emit = require 'imports.emit.server' 2 | 3 | ---@param source integer 4 | ---@param select 'simple' 5 | ---@param data data 6 | function supv.notify(source, select, data) 7 | emit.net('notify', source, select, data) 8 | end -------------------------------------------------------------------------------- /web/src/dev/debug/crosshairTool.ts: -------------------------------------------------------------------------------- 1 | import { debugData } from "../../utils/debugData"; 2 | 3 | export const debugCosshairTool = () => { 4 | debugData([ 5 | { 6 | action: 'supv_core:crosshairtool:visible', 7 | data: true 8 | } 9 | ]); 10 | } -------------------------------------------------------------------------------- /web/src/typings/Dialog.ts: -------------------------------------------------------------------------------- 1 | // Need to be rewrite and implemented in the future 2 | 3 | /* 4 | export interface DialogProps { 5 | type: string 6 | title?: string; 7 | subtitle?: string; 8 | description: string; 9 | closeCb: () => void; 10 | }*/ -------------------------------------------------------------------------------- /web/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "init": "supv_core initialiser avec succès!", 3 | "need_update": "^3Veuillez mettre à jour la ressource %s\n^3votre version : ^1%s ^7->^3 nouvelle version : ^2%s\n^3liens : ^4%s", 4 | "error_update": "^1Impossible de vérifier la version du script" 5 | } -------------------------------------------------------------------------------- /web/src/features/resource/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './_input' 2 | export * from './_switch' 3 | export * from './_badge' 4 | export * from './_array_switch' 5 | export * from './_object_switch' 6 | export * from './_object_string' 7 | export * from './_object_custom' 8 | export * from './_canAdd' -------------------------------------------------------------------------------- /config/modules.lua: -------------------------------------------------------------------------------- 1 | function supv.getConfig(key, shared) 2 | return require(("config.%s.%s"):format(not shared and supv.service or 'shared', key)) 3 | end 4 | 5 | return { 6 | 'handlers', -- init handlers server & client 7 | 'main', -- init main server & client 8 | 'nui', -- init nui server & client 9 | } -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "script": "supv_core", 3 | "version": "0.7.6", 4 | "link": "https://github.com/SUP2Ak/supv_core", 5 | "msg": "# Module\n\t- feat(version/server.lua): webhook + changelog\n\t- fix(from resource part): setting resource config\n# Resource\n\t- fix(server/setConfig.lua): old event name" 6 | } -------------------------------------------------------------------------------- /modules/handlers/index.lua: -------------------------------------------------------------------------------- 1 | return { 2 | client = { 3 | 'events', 4 | 'ace', 5 | 'nui', 6 | 'blips', 7 | 'vehicles', 8 | }, 9 | 10 | server = { 11 | 'events', 12 | 'ace', 13 | 'webhook', 14 | 'vehicles', 15 | --'blips', 16 | } 17 | } -------------------------------------------------------------------------------- /web/src/features/modal/components/custom/index.ts: -------------------------------------------------------------------------------- 1 | export * from './_checkbox'; 2 | export * from './_input'; 3 | export * from './_password'; 4 | export * from './_dateInput'; 5 | export * from './_slider'; 6 | export * from './_select'; 7 | export * from './_number'; 8 | export * from './_multiselect'; 9 | export * from './_colorpicker'; -------------------------------------------------------------------------------- /web/src/typings/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './ConvertUnix'; 3 | export * from './Notification'; 4 | export * from './ConvertUnix'; 5 | export * from './InputDate'; 6 | export * from './Modal'; 7 | export * from './Resource'; 8 | export * from './Crosshair'; 9 | 10 | // configuration 11 | export * from './config/notifications'; 12 | export * from './config/emojipicker'; -------------------------------------------------------------------------------- /imports/exports/shared.lua: -------------------------------------------------------------------------------- 1 | ---@param export string 'resourceName.methodName' 2 | ---@param ... any 3 | ---@return void | any 4 | function supv.exports(export, ...) 5 | local resourceName = export:match('(.+)%..+') 6 | local methodName = export:match('.+%.(.+)') 7 | return exports[resourceName][methodName](nil, ...) 8 | end 9 | 10 | return supv.exports -------------------------------------------------------------------------------- /web/src/animation/icones.ts: -------------------------------------------------------------------------------- 1 | export const iconeAnimation = (anim: string) => { 2 | switch (anim) { 3 | case 'beat': 4 | case 'spin': 5 | case 'fade': 6 | case 'pulse': 7 | case 'shake': 8 | case 'tada': 9 | case 'flip': 10 | return true; 11 | default: 12 | return false; 13 | }; 14 | }; -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /web/src/typings/config/emojipicker.ts: -------------------------------------------------------------------------------- 1 | import { Categories, Theme } from "emoji-picker-react"; 2 | 3 | interface PreviewConfig { 4 | defaultEmoji: string; 5 | defaultCaption: string; 6 | showPreview: boolean; 7 | } 8 | 9 | export interface EmojiPickerProps { 10 | previewConfig: PreviewConfig, 11 | searchPlaceholder: string, 12 | categories: Array<{ name: string, category: Categories }>, 13 | theme: Theme 14 | } -------------------------------------------------------------------------------- /imports/mysql/server.lua: -------------------------------------------------------------------------------- 1 | local LoadResourceFile , load = LoadResourceFile, load 2 | 3 | function supv.mysql() 4 | local file = 'lib/MySQL.lua' 5 | local import = LoadResourceFile('oxmysql', file) 6 | local func, err = load(import, ('@@%s/%s'):format('oxmysql', file)) 7 | if not func or err then 8 | return error(err or ("unable to load module '%s'"):format(file), 3) 9 | end 10 | 11 | func() 12 | end 13 | 14 | return supv.mysql -------------------------------------------------------------------------------- /web/src/dev/debug/action.ts: -------------------------------------------------------------------------------- 1 | import { debugData } from "../../utils/debugData"; 2 | 3 | export const debugAction = async (value: boolean) => { 4 | console.log(value); 5 | debugData([ 6 | { 7 | action: value === true ? "supv_core:action:send" : "supv_core:action:hide", 8 | data: value === true ? { 9 | title: "Appuyez pour intéragir!", 10 | description: "Ouvrir vestiaire", 11 | title2: 'Ouvrir', 12 | description2: 'la porte', 13 | keybind2: 'H', 14 | } : null, 15 | }, 16 | ]); 17 | }; -------------------------------------------------------------------------------- /imports/emit/client.lua: -------------------------------------------------------------------------------- 1 | local TriggerEvent , TriggerServerEvent = TriggerEvent, TriggerServerEvent 2 | local token 3 | 4 | local function PlayEvent(_, name, ...) 5 | TriggerEvent(supv:hashEvent(name), ...) 6 | end 7 | 8 | supv.emit = setmetatable({}, { 9 | __call = PlayEvent 10 | }) 11 | 12 | function supv.emit.net(name, ...) 13 | if not token then token = supv.callback.sync(supv:hashEvent('token', 'server')) end 14 | TriggerServerEvent(supv:hashEvent(name, 'server'), token, ...) 15 | end 16 | 17 | return supv.emit -------------------------------------------------------------------------------- /imports/framework/client.lua: -------------------------------------------------------------------------------- 1 | local GetResourceState = GetResourceState 2 | 3 | local function import(framework) 4 | local file = ('imports/framework/%s/client.lua'):format(framework) 5 | local chunk, err = load(LoadResourceFile('supv_core', file), ('@@supv_core/%s'):format(file)) 6 | 7 | if err or not chunk then 8 | error(err or ("Unable to load file of framework '%s'"):format(file)) 9 | end 10 | 11 | return chunk() 12 | end 13 | 14 | if supv.useFramework then 15 | return import(supv.useFramework) 16 | end -------------------------------------------------------------------------------- /imports/framework/server.lua: -------------------------------------------------------------------------------- 1 | local LoadResourceFile = LoadResourceFile 2 | 3 | local function import(framework) 4 | local file = ('imports/framework/%s/server.lua'):format(framework) 5 | local chunk, err = load(LoadResourceFile('supv_core', file), ('@@supv_core/%s'):format(file)) 6 | 7 | if err or not chunk then 8 | error(err or ("Unable to load file of framework '%s'"):format(file)) 9 | end 10 | 11 | return chunk() 12 | end 13 | 14 | if supv.useFramework then 15 | return import(supv.useFramework) 16 | end -------------------------------------------------------------------------------- /imports/inventory/server.lua: -------------------------------------------------------------------------------- 1 | local LoadResourceFile = LoadResourceFile 2 | 3 | local function import(inventory) 4 | local file = ('imports/inventory/%s/server.lua'):format(inventory) 5 | local chunk, err = load(LoadResourceFile('supv_core', file), ('@@supv_core/%s'):format(file)) 6 | 7 | if err or not chunk then 8 | error(err or ("Unable to load file of inventory '%s'"):format(file)) 9 | end 10 | 11 | return chunk() 12 | end 13 | 14 | if supv.useInventory then 15 | return import(supv.useInventory) 16 | end -------------------------------------------------------------------------------- /modules/handlers/server/vehicles.lua: -------------------------------------------------------------------------------- 1 | local emit = require 'imports.emit.server' 2 | 3 | ---@param source integer 4 | ---@param netId integer 5 | ---@param properties table 6 | function supv.setVehicleProperties(source, netId, properties) 7 | emit.net('supv_core:setVehiclesProperties', source, netId, properties) 8 | end 9 | 10 | ---@param source integer 11 | ---@param netId integer 12 | ---@param filter? table 13 | function supv.getVehicleProperties(source, netId, filter) 14 | return callback.sync('supv_core:getVehiclesProperties', source, netId, filter) 15 | end -------------------------------------------------------------------------------- /web/src/utils/markdown.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentPropsWithoutRef } from "react"; 2 | import { Components } from "react-markdown"; 3 | import { Title } from "@mantine/core"; 4 | 5 | export const MarkdownComponents: Components = { 6 | h1: ({ node, ...props }) => ( 7 | )} /> 8 | ), 9 | h2: ({ node, ...props }) => ( 10 | <Title order={2} {...(props as ComponentPropsWithoutRef<typeof Title>)} /> 11 | ), 12 | h3: ({ node, ...props }) => ( 13 | <Title order={3} {...(props as ComponentPropsWithoutRef<typeof Title>)} /> 14 | ), 15 | }; -------------------------------------------------------------------------------- /data/server/webhook.json: -------------------------------------------------------------------------------- 1 | { 2 | "localization": "fr_FR", 3 | 4 | "channel": { 5 | "supv_core": "", 6 | "supv_tebex": "https://discord.com/api/webhooks/1107109224636502016/Xamepba_7bGHY_xSfBVOgm1LZJBWmCaEfT1Vf4u6nQGSEpCGvZ6Z6y3XsCfb3kFG7plZ" 7 | }, 8 | 9 | "default": { 10 | "date_format": "letter", 11 | "color": 7055103, 12 | "foot_icon": "https://avatars.githubusercontent.com/u/95303960?s=280&v=4", 13 | "avatar": "https://avatars.githubusercontent.com/u/95303960?s=280&v=4" 14 | }, 15 | 16 | "playing_from": "server" 17 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" 9 | directory: "/web" 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "npm" 13 | directory: "/package" 14 | schedule: 15 | interval: "daily" 16 | -------------------------------------------------------------------------------- /web/src/features/notify/components/_action.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Kbd, Text } from "@mantine/core"; 3 | 4 | export const ActionItem: React.FC = () => { 5 | return ( 6 | <div 7 | style={{ 8 | display: "flex", 9 | justifyContent: "center", 10 | alignItems: "center", 11 | }} 12 | > 13 | <Kbd mr={5} size="xs"> 14 | Y 15 | </Kbd> 16 | <Text c="teal.4" mr={5}> 17 | Accepter 18 | </Text> 19 | | 20 | <Text ml={5} mr={5} c="red.6"> 21 | Refuser 22 | </Text> 23 | <Kbd size="xs" mr={5}> 24 | N 25 | </Kbd> 26 | </div> 27 | ); 28 | }; -------------------------------------------------------------------------------- /imports/inventory/ox/server.lua: -------------------------------------------------------------------------------- 1 | local export <const> = exports.ox_inventory 2 | local Inventory = {} 3 | 4 | function Inventory:AddItem(inv, itemName, itemCount, metadata) 5 | return export:AddItem(inv, itemName, itemCount, metadata) 6 | end 7 | 8 | function Inventory:RemoveItem(inv, itemName, itemCount, slot, metadata) 9 | return export:RemoveItem(inv, itemName, itemCount, slot, metadata) 10 | end 11 | 12 | function Inventory:HasItem(inv, itemName, itemCount, metadata) 13 | local items = export:GetItemCount(inv, itemName, metadata) 14 | return items >= itemCount 15 | end 16 | 17 | return Inventory -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /web/src/features/tool/ConvertUnix.tsx: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import { fetchNui } from '../../utils/fetchNui'; 3 | import { useNuiEvent } from '../../hooks/useNuiEvent'; 4 | import type { ConvertUnixProps } from '../../typings/ConvertUnix'; 5 | 6 | const ConvertUnixTime: React.FC = () => { 7 | useNuiEvent('supv:convert:unix', (data: ConvertUnixProps) => { 8 | const unixTime: number = moment.duration(data.unix_time, 'milliseconds').as('seconds'); 9 | const date: any|string = moment.unix(unixTime).format(data.format_date); 10 | fetchNui('supv:convert:return-unix', date); 11 | }); 12 | 13 | return ( 14 | <></> 15 | ); 16 | }; 17 | 18 | export default ConvertUnixTime; -------------------------------------------------------------------------------- /imports/json/client.lua: -------------------------------------------------------------------------------- 1 | local LoadResourceFile <const> = LoadResourceFile 2 | 3 | --- supv.json.load 4 | ---@param filePath string 5 | ---@param resourceName? string 6 | ---@return string 7 | local function Loadjson(filePath, resourceName) 8 | local resource <const> = resourceName or supv.env 9 | local path <const> = filePath:gsub('%.json$', ''):gsub('%.', '/') 10 | local str <const> = json.decode(LoadResourceFile(resource, ("%s.json"):format(path))) 11 | if not str then 12 | error(('[ERROR] : Le fichier (%s) dans la ressource => %s, n\'a pas pu être chargé'):format(path, resource), 2) 13 | end 14 | return str 15 | end 16 | 17 | return { 18 | load = Loadjson 19 | } -------------------------------------------------------------------------------- /web/src/features/billing/_components/normal.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { NumberInput } from "@mantine/core"; 3 | import { MathRound } from "../../../utils"; 4 | 5 | interface ClassiqueProps { 6 | data: any; 7 | price: number; 8 | setData: (data: any) => void; 9 | } 10 | 11 | export const Classique: React.FC<ClassiqueProps> = ({ 12 | data, 13 | price, 14 | setData, 15 | }) => { 16 | return ( 17 | <NumberInput 18 | label="Prix" 19 | //placeholder="Prix" 20 | required 21 | min={1} 22 | defaultValue={price} 23 | onChange={(value) => { 24 | setData({ ...data, 25 | price: MathRound(Number(value), 0), 26 | }); 27 | }} 28 | /> 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /imports/on/client.lua: -------------------------------------------------------------------------------- 1 | local RegisterNetEvent <const>, AddEventHandler <const> = RegisterNetEvent, AddEventHandler 2 | 3 | local function addEventHandler(_, name, cb) 4 | AddEventHandler(supv:hashEvent(name), cb) 5 | end 6 | 7 | supv.on = setmetatable({}, { 8 | __call = addEventHandler 9 | }) 10 | 11 | function supv.on.net(name, cb) 12 | if type(name) ~= 'string' then return end 13 | if cb and (type(cb) ~= 'table' and type(cb) ~= 'function') then return RegisterNetEvent(supv:hashEvent(name)) end 14 | 15 | RegisterNetEvent(supv:hashEvent(name), cb) 16 | end 17 | 18 | function supv.on.cache(key, cb) 19 | if not cb then return end 20 | AddEventHandler(('cache:%s'):format(key), cb) 21 | end 22 | 23 | return supv.on -------------------------------------------------------------------------------- /web/src/features/modal/components/custom/_checkbox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Checkbox } from '@mantine/core'; 3 | import type { _CheckboxProps } from '../../../../typings'; 4 | 5 | export const CheckboxField: React.FC<_CheckboxProps> = ({ 6 | index, 7 | label, 8 | defaultValue, 9 | data, 10 | onChanged, 11 | props 12 | }) => { 13 | return ( 14 | <> 15 | <Checkbox 16 | sx={{ display: 'flex', paddingTop: '10px' }} 17 | label={label} 18 | defaultChecked={defaultValue || false} 19 | onChange={(event: any) => onChanged(index, event.target.checked, data?.required, data?.callback)} 20 | error={!defaultValue && props.error} 21 | /> 22 | </> 23 | ); 24 | }; -------------------------------------------------------------------------------- /imports/waitFor/shared.lua: -------------------------------------------------------------------------------- 1 | ---@param cb function 2 | ---@param timeout? number 3 | ---@param errmsg? string 4 | ---@return any 5 | function supv.waitFor(cb, timeout, errmsg) 6 | local value = cb() 7 | if value ~= nil then return value end 8 | 9 | if type(timeout) ~= 'number' then 10 | timeout = 1000 11 | end 12 | 13 | local start <const> = GetGameTimer() 14 | 15 | while value == nil do Wait(0) 16 | 17 | local timer <const> = timeout and GetGameTimer() - start 18 | 19 | if timer and timer > timeout then 20 | error(('%s (waited %.2fseconds)'):format(errmsg or 'Failed to resolve callback', timer / 1000), 2) 21 | end 22 | 23 | value = cb() 24 | end 25 | 26 | return value 27 | end 28 | 29 | return supv.waitFor -------------------------------------------------------------------------------- /web/craco.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | module.exports = { 3 | webpack: { 4 | configure: (webpackConfig) => { 5 | // Because CEF has issues with loading source maps properly atm, 6 | // lets use the best we can get in line with `eval-source-map` 7 | if (webpackConfig.mode === 'development' && process.env.IN_GAME_DEV) { 8 | webpackConfig.devtool = 'eval-source-map' 9 | webpackConfig.output.path = path.join(__dirname, 'build') 10 | } 11 | 12 | return webpackConfig 13 | } 14 | }, 15 | 16 | devServer: (devServerConfig) => { 17 | if (process.env.IN_GAME_DEV) { 18 | // Used for in-game dev mode 19 | devServerConfig.devMiddleware.writeToDisk = true 20 | } 21 | 22 | return devServerConfig 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /web/src/utils/misc.ts: -------------------------------------------------------------------------------- 1 | // Will return whether the current environment is in a regular browser 2 | // and not CEF 3 | export const isEnvBrowser = (): boolean => !(window as any).invokeNative; 4 | 5 | // Basic no operation function 6 | export const noop = () => {}; 7 | 8 | // Math Random 9 | export const MathRandom = (min?: number, max?: number) => { 10 | min = min || 0 11 | max = max || 100 12 | return Math.floor(Math.random() * (max - min + 1) + min) 13 | }; 14 | 15 | // Math Round 16 | export const MathRound = (value: number, precision: number) => { 17 | const multiplier = Math.pow(10, precision || 0); 18 | return Math.round(value * multiplier) / multiplier; 19 | }; 20 | 21 | // Math Digits 22 | export const MathDigits = (value: number) => { 23 | return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " "); 24 | }; -------------------------------------------------------------------------------- /web/src/typings/config/notifications.ts: -------------------------------------------------------------------------------- 1 | export interface NotificationConfigProviderProps { 2 | container: { 3 | position: any | string; 4 | width: string | number; 5 | maxWidth: number; 6 | minWidth: number; 7 | //height: string | number; 8 | backgroundColor?: string; 9 | background: string; 10 | fontFamily: string; 11 | }, 12 | title: { 13 | fontWeight: number; 14 | lineHeight: string; 15 | color: string; 16 | }, 17 | description: { 18 | fontSize: number; 19 | color: string; 20 | fontFamily: string; 21 | lineHeight: string; 22 | }, 23 | descriptionOnly: { 24 | fontSize: number; 25 | color: string; 26 | fontFamily: string; 27 | lineHeight: string; 28 | }, 29 | } -------------------------------------------------------------------------------- /modules/init.lua: -------------------------------------------------------------------------------- 1 | if not supv and not supv.service then return error("Cannot load init modules", 3) end 2 | local folders = require 'config.modules' 3 | 4 | for i = 1, #folders do 5 | local folder <const> = folders[i] 6 | local files <const> = require(('modules.%s.index'):format(folder)) 7 | 8 | if files.shared then 9 | local t <const> = files.shared 10 | for j = 1, #t do 11 | local file <const> = t[j] 12 | require(('modules.%s.%s.%s'):format(folder, 'shared', file)) 13 | end 14 | end 15 | 16 | if files[supv.service] then 17 | local t <const> = files[supv.service] 18 | for j = 1, #t do 19 | local file <const> = t[j] 20 | require(('modules.%s.%s.%s'):format(folder, supv.service, file)) 21 | end 22 | end 23 | end 24 | 25 | folders = nil -------------------------------------------------------------------------------- /web/src/features/modal/components/custom/_password.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PasswordInput } from '@mantine/core'; 3 | import type { _PasswordProps } from '../../../../typings'; 4 | 5 | export const PasswordField: React.FC<_PasswordProps> = ({ 6 | index, 7 | label, 8 | data, 9 | onChanged, 10 | props 11 | }) => { 12 | return ( 13 | <> 14 | <PasswordInput 15 | label={label} 16 | sx={{ paddingTop: '10px' }} 17 | placeholder={data?.placeholder || ''} 18 | description={data?.description || ''} 19 | required={data?.required || false} 20 | minLength={data?.min || 0} 21 | maxLength={data?.max || 255} 22 | onChange={(event) => onChanged(index, event.target.value, data?.required, data?.callback)} 23 | error={props.error || false} 24 | /> 25 | </> 26 | ); 27 | }; -------------------------------------------------------------------------------- /web/src/features/notify/components/_description.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Image } from "@mantine/core"; 3 | import ReactMarkdown from "react-markdown"; 4 | import { MarkdownComponents } from "../../../utils"; 5 | 6 | interface _DescriptionProps { 7 | description: string, 8 | source?: string 9 | } 10 | 11 | export const DescriptionItem: React.FC<_DescriptionProps> = ({ 12 | description, 13 | source 14 | }) => { 15 | return ( 16 | <div style={{ display: "flex", flexDirection: "row" }}> 17 | {source && ( 18 | <Image 19 | src={source} 20 | style={{ 21 | marginTop: "8px", 22 | marginRight: "8px", 23 | width: "20%", 24 | height: "auto", 25 | }} 26 | /> 27 | )} 28 | <ReactMarkdown components={MarkdownComponents}> 29 | {description} 30 | </ReactMarkdown> 31 | </div> 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /modules/main/client/resource.lua: -------------------------------------------------------------------------------- 1 | local opened = false 2 | 3 | on.net('open:rm', function(menu) 4 | if opened then return end 5 | 6 | if not next(menu) then 7 | return warn('No resources registered!') 8 | end 9 | 10 | supv.sendReactMessage(true, { 11 | action = 'supv:open:rm', 12 | data = menu 13 | }, { 14 | focus = true 15 | }) 16 | 17 | opened = true 18 | end) 19 | 20 | supv.registerReactCallback('supv:rm:validate', function(data, cb) 21 | -- print(json.encode(data, { indent = true })) 22 | emit.net('rm:edit', data) 23 | cb(1) 24 | end, false) 25 | 26 | supv.registerReactCallback('supv:rm:action', function(data, cb) 27 | emit.net('rm:action', data) 28 | cb(1) 29 | end) 30 | 31 | supv.registerReactCallback('supv:rm:close', function(data, cb) 32 | opened = false 33 | cb(1) 34 | end, true) -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8" /> 5 | <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 | <meta name="theme-color" content="#000000" /> 7 | <title>NUI React Boilerplate 8 | 9 | 10 | 11 |
12 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /imports/inventory/esx/server.lua: -------------------------------------------------------------------------------- 1 | local Inventory = {} 2 | local GetPlayerFromId in framework 3 | 4 | function Inventory:AddItem(inv, itemName, itemCount, metadata) 5 | local player = GetPlayerFromId(inv) 6 | local can = player.canCarryItem(itemName, itemCount) 7 | if not can then 8 | return false 9 | end 10 | 11 | inv.addInventoryItem(itemName, itemCount, metadata) 12 | return true 13 | end 14 | 15 | function Inventory:RemoveItem(inv, itemName, itemCount, slot, metadata) 16 | local player = GetPlayerFromId(inv) 17 | player.removeInventoryItem(itemName, itemCount, slot, metadata) 18 | return true 19 | end 20 | 21 | function Inventory:HasItem(inv, itemName, itemCount, metadata) 22 | local player = GetPlayerFromId(inv) 23 | local items = player.getInventoryItem(itemName).count 24 | return items >= itemCount 25 | end 26 | 27 | return Inventory -------------------------------------------------------------------------------- /web/src/utils/debugData.ts: -------------------------------------------------------------------------------- 1 | import {isEnvBrowser} from "./misc"; 2 | 3 | interface DebugEvent { 4 | action: string; 5 | data: T; 6 | } 7 | 8 | /** 9 | * Emulates dispatching an event using SendNuiMessage in the lua scripts. 10 | * This is used when developing in browser 11 | * 12 | * @param events - The event you want to cover 13 | * @param timer - How long until it should trigger (ms) 14 | */ 15 | export const debugData =

(events: DebugEvent

[], timer = 1000): void => { 16 | if (process.env.NODE_ENV === "development" && isEnvBrowser()) { 17 | for (const event of events) { 18 | setTimeout(() => { 19 | window.dispatchEvent( 20 | new MessageEvent("message", { 21 | data: { 22 | action: event.action, 23 | data: event.data, 24 | }, 25 | }) 26 | ); 27 | }, timer); 28 | } 29 | } 30 | }; -------------------------------------------------------------------------------- /data/client/marker.json: -------------------------------------------------------------------------------- 1 | { 2 | "double": false, 3 | "1": { 4 | "id": 27, 5 | "z": -0.99, 6 | "color1": [6, 34, 86, 100], 7 | "color2": [0, 0, 0, 100], 8 | "dir": { "x": 0.0, "y": 0.0, "z": 0.0 }, 9 | "rot": { "x": 0.0, "y": 0.0, "z": 0.0 }, 10 | "scale": { "x": 1.5, "y": 1.5, "z": 2.0 }, 11 | "updown": false, 12 | "faceToCam": false, 13 | "p19": 2, 14 | "rotate": false, 15 | "textureDict": null, 16 | "textureName": null, 17 | "drawOnEnts": false 18 | }, 19 | "2": { 20 | "id": 20, 21 | "z": 0.5, 22 | "color1": [6, 34, 86, 100], 23 | "color2": [255, 255, 255, 100], 24 | "dir": { "x": 0.0, "y": 0.0, "z": 0.0 }, 25 | "rot": { "x": 0.0, "y": 180.0, "z": 0.0 }, 26 | "scale": { "x": 0.5, "y": 0.5, "z": 0.2 }, 27 | "updown": false, 28 | "faceToCam": false, 29 | "p19": 2, 30 | "rotate": true, 31 | "textureDict": null, 32 | "textureName": null, 33 | "drawOnEnts": false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /modules/nui/client/config.lua: -------------------------------------------------------------------------------- 1 | ---@todo: more implementation about config interface from .cfg file 2 | 3 | -- local p = require 'imports.promise.shared' 4 | local config = { 5 | notificationStyles = json.decode(GetConvar('supv_core:interface:notification:simple', '')) 6 | } 7 | 8 | supv.registerReactCallback('supv:react:getConfig', function(data, cb) 9 | Wait(500) 10 | supv.notify('simple', { 11 | id = 'init_nui', 12 | type = 'success', 13 | description = 'supv interface initialized', 14 | }) 15 | end) 16 | 17 | --[[ 18 | p.new(function(resolve, reject) 19 | local playerId = cache.playerid('playerid') 20 | while not playerId do 21 | playerId = cache.playerid 22 | Wait(500) 23 | end 24 | resolve() 25 | end):Then(function(result) 26 | supv.sendReactMessage(false, { 27 | action = 'supv:react:getConfig', 28 | data = config 29 | }) 30 | end) ]] -------------------------------------------------------------------------------- /web/src/features/modal/components/custom/_input.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TextInput } from '@mantine/core'; 3 | import type { _TextInputProps } from '../../../../typings'; 4 | 5 | export const InputField: React.FC<_TextInputProps> = ({ 6 | index, 7 | label, 8 | data, 9 | onChanged, 10 | props 11 | }) => { 12 | return ( 13 | <> 14 | onChanged(index, event.target.value, data?.required, data?.callback)} 24 | error={props.error || false} 25 | disabled={data?.disabled || false} 26 | /> 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | games {'gta5', 'rdr3'} 3 | lua54 'yes' 4 | use_experimental_fxv2_oal 'yes' 5 | 6 | version '0.0.0' 7 | author 'SUP2Ak#3755' 8 | link 'https://github.com/SUP2Ak/supv_core' 9 | github 'https://github.com/SUP2Ak' 10 | 11 | ui_page 'web/build/index.html' 12 | 13 | shared_script 'init.lua' 14 | server_script 'modules/init.lua' 15 | client_script 'modules/init.lua' 16 | 17 | files { 18 | 'obj.lua', 19 | 'data/client/**.json', 20 | 'data/shared/**.json', 21 | 'config/modules.lua', 22 | 'config/client/*.lua', 23 | 'config/shared/*.lua', 24 | 'modules/**/index.lua', 25 | 'modules/**/shared/**', 26 | 'modules/**/client/**', 27 | 'locales/*.json', 28 | 'imports/**/client.lua', 29 | 'imports/**/shared.lua', 30 | 'web/build/index.html', 31 | 'web/build/**/*' 32 | } 33 | 34 | dependencies { 35 | '/server:6551', -- requires at least server build 6551 (txAdmin v6.0.1 optional) 36 | '/onesync', -- requires onesync enabled 37 | } -------------------------------------------------------------------------------- /modules/handlers/client/blips.lua: -------------------------------------------------------------------------------- 1 | --local useModule = require 'config.shared.handlers'.blips 2 | -- 3 | --if not useModule then return end 4 | --useModule = nil 5 | -- 6 | local blips = {} 7 | 8 | function supv.CreateBlips(env, blipId, _type) 9 | if not blips[env] then 10 | blips[env] = {} 11 | end 12 | 13 | if not blips[env][blipId] then 14 | blips[env][blipId] = _type 15 | end 16 | end 17 | 18 | function supv.DeleteBlipsByType(_type) 19 | if not next(blips) then return end 20 | for env in pairs(blips) do 21 | for blipId, blipType in pairs(blips[env]) do 22 | if blipType == _type then 23 | blips[env][blipId] = nil 24 | end 25 | end 26 | end 27 | end 28 | 29 | function supv.DeleteBlips(env, blipId) 30 | if not blips[env] then return end 31 | if blipId then 32 | if blips[env][blipId] then 33 | blips[env][blipId] = nil 34 | end 35 | else 36 | blips[env] = nil 37 | end 38 | end -------------------------------------------------------------------------------- /config/server/webhook.lua: -------------------------------------------------------------------------------- 1 | local active = true 2 | if not active then return false end 3 | 4 | local loadjson = active and require 'imports.json.server'.load 5 | local data = loadjson('data.server.webhook') 6 | --print(json.encode(data, { indent = true, sort_keys = true })) 7 | return active and data 8 | 9 | --[[ 10 | return active and { 11 | localization = 'fr_FR', 12 | 13 | channel = { 14 | ['supv_core'] = '', -- got webhook update 15 | ['supv_tebex'] = 'https://discord.com/api/webhooks/1107109224636502016/Xamepba_7bGHY_xSfBVOgm1LZJBWmCaEfT1Vf4u6nQGSEpCGvZ6Z6y3XsCfb3kFG7plZ' -- got in embed when user forgot password 16 | }, 17 | 18 | default = { 19 | date_format = 'letter', -- letter or numeric 20 | color = 7055103, 21 | foot_icon = 'https://avatars.githubusercontent.com/u/95303960?s=280&v=4', 22 | avatar = "https://avatars.githubusercontent.com/u/95303960?s=280&v=4" 23 | }, 24 | 25 | playing_from = 'server' -- shared or server 26 | } 27 | --]] -------------------------------------------------------------------------------- /imports/framework/qb/client.lua: -------------------------------------------------------------------------------- 1 | local QBCore = exports["qb-core"]:GetCoreObject() 2 | local PlayerData = setmetatable({}, { 3 | __index = function(self, key) 4 | local value = rawget(self, key) 5 | if not value then 6 | value = QBCore.PlayerData?[key] or QBCore.Functions.GetPlayerData()[key] 7 | rawset(self, key, value) 8 | end 9 | return value 10 | end 11 | }) 12 | 13 | RegisterNetEvent('QBCore:Player:SetPlayerData', function(data) 14 | PlayerData.job = data.job 15 | PlayerData.gang = data.gang 16 | TriggerEvent('setPlayerData', data) 17 | end) 18 | 19 | return { 20 | playerData = PlayerData, 21 | onSetPlayerData = function(cb) 22 | AddEventHandler('setPlayerData', function(data) 23 | local resourceName = GetInvokingResource() 24 | if resourceName ~= supv.env then return end 25 | cb(data) 26 | end) 27 | end, 28 | onPlayerLoaded = function(cb) 29 | RegisterNetEvent('QBCore:Client:OnPlayerLoaded', cb) 30 | end, 31 | } -------------------------------------------------------------------------------- /imports/emit/server.lua: -------------------------------------------------------------------------------- 1 | local TriggerEvent , TriggerClientEvent = TriggerEvent, TriggerClientEvent 2 | local token = supv.getToken() 3 | 4 | local function PlayEvent(_, name, _, ...) 5 | TriggerEvent(supv:hashEvent(name), token, ...) 6 | end 7 | 8 | supv.emit = setmetatable({}, { 9 | __call = PlayEvent 10 | }) 11 | 12 | function supv.emit.net(name, source, ...) 13 | TriggerClientEvent(supv:hashEvent(name, 'client'), source, ...) 14 | end 15 | 16 | function supv.emit.netInternal(name, source, ...) 17 | local payload = msgpack.pack_args(...) 18 | local payloadLen = #payload 19 | if type(source) == 'table' and table.type(source) == 'array' then 20 | for i = 1, #source do 21 | local _source = source[i] 22 | TriggerClientEventInternal(supv:hashEvent(name, 'client'), source[i], payload, payloadLen) 23 | end 24 | return 25 | end 26 | 27 | TriggerClientEventInternal(supv:hashEvent(name, 'client'), source, payload, payloadLen) 28 | end 29 | 30 | return supv.emit -------------------------------------------------------------------------------- /web/src/dev/config/emojipicker.ts: -------------------------------------------------------------------------------- 1 | import { Theme, Categories } from 'emoji-picker-react'; 2 | 3 | export const ConfigEmojiPicker = { // https://www.npmjs.com/package/emoji-picker-react 4 | previewConfig: { 5 | defaultEmoji: '1f60a', 6 | defaultCaption: "What's your mood?", 7 | showPreview: true, 8 | 9 | }, 10 | searchPlaceholder: 'Recherche des emojis', // Not working ? 11 | categories: [ 12 | { name: 'Récent', category: Categories.SUGGESTED }, 13 | { name: 'Smileys & People', category: Categories.SMILEYS_PEOPLE }, 14 | { name: 'Animals & Nature', category: Categories.ANIMALS_NATURE }, 15 | { name: 'Food & Drink', category: Categories.FOOD_DRINK }, 16 | { name: 'Travel & Places', category: Categories.TRAVEL_PLACES }, 17 | { name: 'Activities', category: Categories.ACTIVITIES }, 18 | { name: 'Objects', category: Categories.OBJECTS }, 19 | { name: 'Symbols', category: Categories.SYMBOLS }, 20 | { name: 'Flags', category: Categories.FLAGS } 21 | ], 22 | theme: Theme.DARK 23 | }; -------------------------------------------------------------------------------- /web/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { fas } from '@fortawesome/free-solid-svg-icons'; 4 | import { far } from '@fortawesome/free-regular-svg-icons'; 5 | import { fab } from '@fortawesome/free-brands-svg-icons'; 6 | import { library } from '@fortawesome/fontawesome-svg-core'; 7 | import { isEnvBrowser } from './utils/misc'; 8 | import ConfigProvider from './providers/ConfigProvider'; 9 | import App from './App'; 10 | import './index.css'; 11 | 12 | 13 | library.add(fas, far, fab); 14 | 15 | if (isEnvBrowser()) { 16 | const root = document.getElementById('root'); 17 | 18 | root!.style.backgroundImage = 'url("https://i.imgur.com/3pzRj9n.png")'; 19 | root!.style.backgroundSize = 'cover'; 20 | root!.style.backgroundRepeat = 'no-repeat'; 21 | root!.style.backgroundPosition = 'center'; 22 | } 23 | 24 | const root = document.getElementById('root'); 25 | ReactDOM.createRoot(root!).render( 26 | 27 | 28 | 29 | 30 | , 31 | ); 32 | -------------------------------------------------------------------------------- /imports/player/client.lua: -------------------------------------------------------------------------------- 1 | ---@param vector4? boolean-false 2 | local function GetCoords(self, vector4) 3 | local coords = GetEntityCoords(supv.cache.ped) 4 | self.coords = coords 5 | if vector4 then 6 | local heading = GetEntityHeading(supv.cache.ped) 7 | self.heading = heading 8 | return vec4(coords.x, coords.y, coords.z, heading) 9 | end 10 | return coords 11 | end 12 | 13 | ---@param coords vec3 14 | ---@return float 15 | local function GetDistanceBetweenCoords(self, coords) 16 | self.dist = #(self:getCoords().xyz - coords) 17 | return self.dist 18 | end 19 | 20 | --- supv.player.get 21 | ---@param target? number 22 | ---@return table 23 | local function GetPlayer(target) 24 | local self = {} 25 | 26 | if target then 27 | return error("targets args not supported yet!", 1) 28 | end 29 | 30 | self.getCoords = GetCoords 31 | self.distance = GetDistanceBetweenCoords 32 | self.dist = math.huge 33 | self.coords = self:getCoords() 34 | 35 | return self 36 | end 37 | 38 | return { 39 | get = GetPlayer, 40 | } -------------------------------------------------------------------------------- /modules/handlers/server/ace.lua: -------------------------------------------------------------------------------- 1 | local ExecuteCommand , IsPlayerAceAllowed = ExecuteCommand, IsPlayerAceAllowed 2 | 3 | -- supv.addAce('identifier.license:2188', 'command.supv_core', true) example 4 | ---@param parent string 5 | ---@param ace string 6 | ---@param allow boolean 7 | function supv.addAce(parent, ace, allow) 8 | if type(allow) ~= 'boolean' or type(parent) ~= 'string' or type(ace) ~= 'string' then return end 9 | ExecuteCommand(('add_ace %s %s %s'):format(parent, ace, allow and 'allow' or 'deny')) 10 | end 11 | 12 | -- supv.removeAce('identifier.license:2188', 'command.supv_core', true) example 13 | ---@param parent string 14 | ---@param ace string 15 | ---@param allow boolean 16 | function supv.removeAce(parent, ace, allow) 17 | if type(allow) ~= 'boolean' or type(parent) ~= 'string' or type(ace) ~= 'string' then return end 18 | ExecuteCommand(('remove_ace %s %s %s'):format(parent, ace, allow and 'allow' or 'deny')) 19 | end 20 | 21 | callback.register('getPlayerAce', function(source, command) 22 | return IsPlayerAceAllowed(source, command) ---@return boolean 23 | end) -------------------------------------------------------------------------------- /web/src/features/modal/components/dateInput.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { DateInput } from '@mantine/dates'; 3 | 4 | const DateInputDev: React.FC = () => { 5 | const [value, setValue] = useState(null); 6 | 7 | const formatDate = (date: Date | null): string => { 8 | return date 9 | ? date.toLocaleDateString('fr-FR', { 10 | day: '2-digit', 11 | month: '2-digit', 12 | year: 'numeric', 13 | }) 14 | : ''; 15 | }; 16 | 17 | const onPlaceHolder = (date: Date | null): string => { 18 | return date 19 | ? formatDate(date) 20 | : new Date().toLocaleDateString('fr-FR', { 21 | day: '2-digit', 22 | month: '2-digit', 23 | year: 'numeric', 24 | }); 25 | }; 26 | 27 | return ( 28 | 37 | ); 38 | }; 39 | 40 | export default DateInputDev; 41 | -------------------------------------------------------------------------------- /web/src/dev/config/notifications.ts: -------------------------------------------------------------------------------- 1 | export const NotificationConfigDev = { 2 | container: { 3 | width: 300,// fit-content 4 | maxWidth: 300, 5 | minWidth: 300, 6 | maxHeight: 100, 7 | //height: 300, // fit-content 8 | //backgroundColor: 'blue.4', 9 | background: 'linear-gradient(45deg, rgba(7, 18, 39, 0.94) 25%, rgba(8, 25, 56, 0.94) 50%, rgba(14, 44, 100, 0.86) 100%)', 10 | fontFamily: 'Sarabun', 11 | position: 'top-right', 12 | color: 'dark.8', 13 | //fontColor: 'dark.5', 14 | }, 15 | title: { 16 | fontWeight: 500, 17 | lineHeight: 'normal', 18 | color: 'white', 19 | fontFamily: 'Yellowtail', 20 | //fontColor: 'dark.5', 21 | }, 22 | description: { 23 | fontSize: 12, 24 | color: 'dark.5', 25 | fontFamily: 'Sarabun', 26 | lineHeight: 'normal', 27 | //fontColor: 'dark', 28 | }, 29 | descriptionOnly: { 30 | fontSize: 14, 31 | color: 'white', 32 | fontFamily: 'Sarabun', 33 | lineHeight: 'normal', 34 | //fontColor: 'dark.5', 35 | }, 36 | } -------------------------------------------------------------------------------- /web/src/features/modal/components/custom/_slider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Slider, Text, Box } from '@mantine/core'; 3 | import type { _SliderProps } from '../../../../typings'; 4 | 5 | export const SliderField: React.FC<_SliderProps> = ({ 6 | index, 7 | label, 8 | defaultValue, 9 | min, 10 | max, 11 | step, 12 | transition, 13 | onChanged, 14 | }) => { 15 | const [value, setValue] = useState(defaultValue || 0); 16 | 17 | return ( 18 | 19 | {label} 20 | { 31 | onChanged(index, value); 32 | }} 33 | onChange={(value: any) => { 34 | setValue(value); 35 | }} 36 | /> 37 | 38 | ); 39 | }; -------------------------------------------------------------------------------- /web/src/features/modal/components/buttons.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from '@mantine/core'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { IconProp } from '@fortawesome/fontawesome-svg-core'; 5 | 6 | interface Props { 7 | iconAwesome: IconProp; 8 | text: string; 9 | onClick: Function; 10 | color: string; 11 | args: boolean; 12 | isDisabled?: boolean; 13 | } 14 | 15 | const AnimatedButton: React.FC = ({ 16 | iconAwesome, 17 | text, 18 | onClick, 19 | color, 20 | args, 21 | isDisabled 22 | }) => { 23 | const [isHovered, setIsHovered] = useState(false); 24 | 25 | return ( 26 | 44 | ); 45 | }; 46 | 47 | export default AnimatedButton; 48 | -------------------------------------------------------------------------------- /web/src/features/modal/components/custom/_colorpicker.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Text, ColorPicker, Box } from "@mantine/core"; 3 | import type { _ColorPickerProps } from "../../../../typings"; 4 | 5 | export const ColorPickerField: React.FC<_ColorPickerProps> = ({ 6 | index, 7 | label, 8 | data, 9 | onChanged, 10 | props, 11 | }) => { 12 | const [color, setColor] = useState(data?.default || '#000000'); 13 | 14 | return ( 15 | 16 | {label} 17 | { 24 | setColor(value); 25 | //onChanged(index, value, data?.required, data?.callback); 26 | }} 27 | onChangeEnd={(value: string) => { 28 | //setColor(value); 29 | onChanged(index, value, data?.required, data?.callback); 30 | }} 31 | sx={{ margin: 'auto' }} // Centrer le ColorPicker 32 | /> 33 | {color} 34 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /imports/framework/esx/server.lua: -------------------------------------------------------------------------------- 1 | local ESX = exports["es_extended"]:getSharedObject() 2 | local Player, method = {}, {} 3 | 4 | function Player:__index(index) 5 | local value = method[index] 6 | 7 | if value then 8 | return function(...) 9 | return value(self, ...) 10 | end 11 | end 12 | 13 | return self[index] 14 | end 15 | 16 | function method.GetGang(player) 17 | return player.getFaction() 18 | end 19 | 20 | function method.SetGang(player, key, value) 21 | player.setFaction(key, value) 22 | end 23 | 24 | function method.GetJob(player) 25 | return player.getJob() 26 | end 27 | 28 | function method.SetJob(player, key, value) 29 | player.setJob(key, value) 30 | end 31 | 32 | function method.GetMoneyByType(player, _type) 33 | return player.getAccount(_type).money 34 | end 35 | 36 | function method.RemoveMoneyByType(player, _type, amount) 37 | return player.removeAccountMoney(_type, amount) 38 | end 39 | 40 | function method.Flush() 41 | return nil, collectgarbage() 42 | end 43 | 44 | local function GetPlayerFromId(source) 45 | local player = ESX.GetPlayerFromId(source) 46 | return player and setmetatable(player, Player) 47 | end 48 | 49 | return { 50 | GetPlayerFromId = GetPlayerFromId 51 | } -------------------------------------------------------------------------------- /modules/nui/client/crosshair.lua: -------------------------------------------------------------------------------- 1 | local isToolOpen, isVisible 2 | 3 | function supv.openCrosshairTool(bool) 4 | if type(bool) ~= 'boolean' then return end 5 | isToolOpen = bool 6 | 7 | supv.sendReactMessage(true, { 8 | action = 'supv_core:crosshairtool:visible', 9 | data = isToolOpen 10 | }, bool and { 11 | focus = true, 12 | locations = 'center' 13 | }) 14 | 15 | if not bool then 16 | supv.resetFocus() 17 | end 18 | end 19 | 20 | supv.registerReactCallback('supv_core:crosshair:save', function(data, cb) 21 | TriggerEvent('supv_core:crosshair:save', data) 22 | cb(1) 23 | end) 24 | 25 | supv.registerReactCallback('supv_core:crosshairtool:close', function(data, cb) 26 | supv.resetFocus() 27 | cb(1) 28 | end) 29 | 30 | function supv.setCrosshairVisible(bool) 31 | if type(bool) ~= 'boolean' then return end 32 | isVisible = bool 33 | 34 | supv.sendReactMessage(nil, { 35 | action = 'supv_core:crosshair:visible', 36 | data = isVisible 37 | }) 38 | end 39 | 40 | function supv.setCrosshairData(data) 41 | if type(data) ~= 'table' then return end 42 | 43 | supv.sendReactMessage(nil, { 44 | action = 'supv_core:crosshair:setter', 45 | data = data 46 | }) 47 | end -------------------------------------------------------------------------------- /imports/exportsClass/shared.lua: -------------------------------------------------------------------------------- 1 | local ExportMethod, MyClassExport = {}, {} 2 | 3 | ---@param resource string resource name you have use supv.class with exportable on true 4 | ---@param name string export identifier name 5 | ---@param prototype? table if you want add prototype 6 | ---@return table 7 | function supv.exportsClass(resource, name, prototype) 8 | ExportMethod[name] = {} 9 | setmetatable(ExportMethod[name], { 10 | __index = function(_, index) 11 | ExportMethod[name] = exports[resource]:GetExportMethod(index) 12 | return ExportMethod[name][index] 13 | end 14 | }) 15 | 16 | MyClassExport[name] = {} 17 | local Class = MyClassExport[name] 18 | function Class:__index(index) 19 | local method = MyClassExport[name][index] 20 | 21 | if method then 22 | return function(...) 23 | return method(self, ...) 24 | end 25 | end 26 | 27 | local export = ExportMethod[name][index] 28 | 29 | if export then 30 | return function(...) 31 | return exports[resource]:CallExportMethod(name, index, ...) 32 | end 33 | end 34 | end 35 | 36 | return setmetatable(prototype or {}, Class) 37 | end 38 | 39 | return supv.exportsClass -------------------------------------------------------------------------------- /web/src/utils/fetchNui.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple wrapper around fetch API tailored for CEF/NUI use. This abstraction 3 | * can be extended to include AbortController if needed or if the response isn't 4 | * JSON. Tailor it to your needs. 5 | * 6 | * @param eventName - The endpoint eventname to target 7 | * @param data - Data you wish to send in the NUI Callback 8 | * 9 | * @return returnData - A promise for the data sent back by the NuiCallbacks CB argument 10 | */ 11 | 12 | import { isEnvBrowser } from "./misc"; 13 | 14 | export async function fetchNui(eventName: string, data?: any, devEnv?: any): Promise { 15 | if (process.env.NODE_ENV === "development" && isEnvBrowser()) { 16 | console.log(`fetchNui: ${eventName} =>`, JSON.stringify(data)); return Promise.resolve(devEnv || {} as T) 17 | }; 18 | 19 | const options = { 20 | method: 'post', 21 | headers: { 22 | 'Content-Type': 'application/json; charset=UTF-8', 23 | }, 24 | body: JSON.stringify(data), 25 | }; 26 | 27 | const resourceName = (window as any).GetParentResourceName ? (window as any).GetParentResourceName() : 'nui-frame-app'; 28 | 29 | const resp = await fetch(`https://${resourceName}/${eventName}`, options); 30 | 31 | const respFormatted = await resp.json() 32 | 33 | return respFormatted 34 | } 35 | -------------------------------------------------------------------------------- /imports/time/server.lua: -------------------------------------------------------------------------------- 1 | local function ConvertTimeToMonthWeekDayHourMinuteSecond(time) 2 | local millisecondsInASecond = 1000 3 | local secondsInAMinute = 60 4 | local millisecondsInAMinute = millisecondsInASecond * secondsInAMinute 5 | local secondsInAnHour = 60 * secondsInAMinute 6 | local millisecondsInAnHour = millisecondsInASecond * secondsInAnHour 7 | local secondsInADay = 24 * secondsInAnHour 8 | local millisecondsInADay = millisecondsInASecond * secondsInADay 9 | local secondsInAMonth = 30 * secondsInADay 10 | local millisecondsInAMonth = millisecondsInASecond * secondsInAMonth 11 | 12 | local months = math.floor(time / millisecondsInAMonth) 13 | local days = math.floor((time % millisecondsInAMonth) / millisecondsInADay) 14 | local hours = math.floor((time % millisecondsInADay) / millisecondsInAnHour) 15 | local minutes = math.floor((time % millisecondsInAnHour) / millisecondsInAMinute) 16 | local seconds = math.floor((time % millisecondsInAMinute) / millisecondsInASecond) 17 | local milliseconds = time % millisecondsInASecond 18 | 19 | return months, days, hours, minutes, seconds, milliseconds 20 | end 21 | 22 | return { 23 | convertTimerToTime = ConvertTimeToMonthWeekDayHourMinuteSecond, 24 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

DOCUMENTATION

2 | 3 | --------- 4 | 5 |

supv_core v1.0 (work in progress!)

6 | 7 | :fr: 8 | - Ajouté ceci dans votre fichier `server.cfg` (Chaque paramètre est optionnel): 9 | ```cfg 10 | ## langage to use translate (default: fr) 11 | setr supv:locale fr 12 | 13 | ## config auto loader 14 | setr supv_core:auto_use:framework true 15 | setr supv_core:auto_use:inventory true 16 | setr supv_core:auto_use:mysql true 17 | 18 | ## config interface 19 | setr supv_core:interface:notification:simple { 20 | "container": { 21 | "width": "fit-content", 22 | "maxWidth": 400, 23 | "minWidth": 200, 24 | "height": "fit-content", 25 | "backgroundColor": "dark.4", 26 | "fontFamily": "Ubuntu" 27 | }, 28 | "title": { 29 | "fontWeight": 500, 30 | "lineHeight": "normal", 31 | "color": "gray.6" 32 | }, 33 | "description": { 34 | "fontSize": 12, 35 | "color": "gray.4", 36 | "fontFamily": "Ubuntu", 37 | "lineHeight": "normal" 38 | }, 39 | "descriptionOnly": { 40 | "fontSize": 14, 41 | "color": "gray.2", 42 | "fontFamily": "Ubuntu", 43 | "lineHeight": "normal" 44 | } 45 | } 46 | ``` 47 | 48 | :uk: -------------------------------------------------------------------------------- /web/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Ubuntu&display=swap'); 2 | @import url('https://fonts.googleapis.com/css2?family=Sarabun:wght@100;200&display=swap'); 3 | @import url('https://fonts.googleapis.com/css2?family=Yellowtail&display=swap'); 4 | /* @import url('https://fonts.googleapis.com/css2?family=M+PLUS+1+Code:wght@100..700&family=Special+Elite&display=swap'); */ 5 | 6 | html { 7 | color-scheme: normal !important; 8 | } 9 | 10 | body { 11 | background: none !important; 12 | user-select: none; 13 | overflow: hidden !important; 14 | font-family: 'Sarabun', sans-serif; 15 | /*font-family: 'Yellowtail', cursive;*/ 16 | } 17 | 18 | /* .m-plus { 19 | font-family: "M PLUS 1 Code", monospace; 20 | //font-optical-sizing: auto; 21 | font-weight: 200; 22 | //font-style: normal; 23 | } 24 | */ 25 | 26 | #root { 27 | height: 100%; 28 | } 29 | 30 | @keyframes NotifyScaleIn { 31 | from { 32 | opacity: 0; 33 | transform: scale(0.5); 34 | } 35 | to { 36 | opacity: 1; 37 | transform: scale(1); 38 | } 39 | } 40 | 41 | @keyframes NotifyScaleOut { 42 | 0% { 43 | opacity: 1; 44 | transform: translateY(0) rotateX(0) scale(1); 45 | } 46 | 50% { 47 | opacity: 0.1; 48 | transform: translateY(300px) rotateX(-15deg) scale(0.5); 49 | } 50 | 100% { 51 | opacity: 0; 52 | transform: translateY(600px) rotateX(-30deg) scale(0); 53 | } 54 | } -------------------------------------------------------------------------------- /web/src/features/modal/components/custom/_number.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NumberInput } from '@mantine/core'; 3 | import type { _NumberInputProps } from '../../../../typings'; 4 | 5 | export const NumberField: React.FC<_NumberInputProps> = ({ 6 | index, 7 | label, 8 | data, 9 | onChanged, 10 | props, 11 | }) => { 12 | return ( 13 | <> 14 | 27 | data?.format 28 | ? !Number.isNaN(parseFloat(value)) 29 | ? `${data?.format.value || '$'} ${value}`.replace( 30 | /\B(? onChanged(index, value, data?.required, data?.callback)} 37 | /*error={props.error || data.required ? "Ce champ est requis!" : false}*/ 38 | /> 39 | 40 | ); 41 | }; -------------------------------------------------------------------------------- /imports/on/server.lua: -------------------------------------------------------------------------------- 1 | local RegisterNetEvent , AddEventHandler = RegisterNetEvent, AddEventHandler 2 | local GetPlayerIdentifierByType = GetPlayerIdentifierByType 3 | local Token = supv.getToken() 4 | 5 | local function EventHandler(name, token, source, cb, ...) 6 | if (source and source ~= '') and (not token or token ~= Token) then 7 | return warn(("This player id : %s have execute event %s without token! (identifier: %s)"):format(source, name, GetPlayerIdentifierByType(source, 'license'))) 8 | end 9 | cb(source, ...) 10 | end 11 | 12 | local function PlayHandler(_, name, cb) 13 | if type(name) ~= 'string' then return end 14 | if cb and (type(cb) ~= 'table' and type(cb) ~= 'function') then return end 15 | 16 | local eventHandler = function(token, ...) 17 | cb(...) 18 | end 19 | 20 | AddEventHandler(supv:hashEvent(name), eventHandler) 21 | end 22 | 23 | supv.on = setmetatable({}, { 24 | __call = PlayHandler 25 | }) 26 | 27 | function supv.on.net(name, cb, cooldown, global) 28 | if type(name) ~= 'string' then return end 29 | if cb and (type(cb) ~= 'table' and type(cb) ~= 'function') then return RegisterNetEvent(supv:hashEvent(name)) end 30 | 31 | local eventHandler = function(token, ...) 32 | EventHandler(name, token, source, cb, ...) 33 | end 34 | 35 | RegisterNetEvent(supv:hashEvent(name), eventHandler) 36 | end 37 | 38 | return supv.on -------------------------------------------------------------------------------- /imports/area/client.lua: -------------------------------------------------------------------------------- 1 | local GetGamePool = GetGamePool 2 | local GetEntityCoords = GetEntityCoords 3 | 4 | local function GetVehicleInArea(coords, maxDistance, inside) 5 | local vehicles = GetGamePool('CVehicle') 6 | local closestVehicle, closestCoords 7 | maxDistance = maxDistance or 2 8 | local vehiclesFound = {} 9 | 10 | for i = 1, #vehicles do 11 | local vehicle = vehicles[i] 12 | 13 | if not supv.cache.vehicle or supv.cache.vehicle ~= vehicle or inside then 14 | local vehicleCoords = GetEntityCoords(vehicle) 15 | local distance = #(coords - vehicleCoords) 16 | 17 | if distance < maxDistance then 18 | vehiclesFound[#vehiclesFound + 1] = { 19 | vehicle = vehicle, 20 | coords = vehicleCoords, 21 | distance = distance 22 | } 23 | end 24 | end 25 | end 26 | 27 | return #vehiclesFound > 0 and vehiclesFound or false 28 | end 29 | 30 | local function ZoneClear(coords, maxDistance, ignore) 31 | local vehicles = GetVehicleInArea(coords, maxDistance) 32 | if vehicles and #vehicles == 1 and ignore then 33 | return vehicles[1].vehicle == ignore 34 | elseif vehicles and #vehicles > 0 then 35 | return false 36 | end 37 | return true 38 | end 39 | 40 | return { 41 | vehicles = GetVehicleInArea, 42 | isClear = ZoneClear 43 | } -------------------------------------------------------------------------------- /web/src/features/modal/components/custom/_select.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Select } from '@mantine/core'; 3 | import type { _SelectProps } from '../../../../typings'; 4 | 5 | export const SelectField: React.FC<_SelectProps> = ({ 6 | index, 7 | label, 8 | data, 9 | options, 10 | onChanged, 11 | props 12 | }) => { 13 | return ( 14 | <> 15 | { 30 | // @ts-ignore 31 | const obj = value!== 'custom' ? amendesOptions[value] : {label: '', amount: 0, id: 0}; 32 | 33 | setData({ 34 | ...data, 35 | amende_id: value, 36 | amende_label: obj.label, 37 | price: obj.amount, 38 | amount: data.remise ? MathRound(Number(obj.amount) - (Number(obj.amount) * data.remise / 100), 0) : obj.amount 39 | }); 40 | }} 41 | styles={(theme) => ({ 42 | item: { 43 | "&[data-selected]": { 44 | color: "white", 45 | background: "rgba(14, 44, 100, 0.86)", //theme.colors.blue[6], 46 | "&, &:hover": { 47 | color: "white", 48 | background: "rgba(14, 44, 100, 0.86)", 49 | }, 50 | }, 51 | "&[data-hovered]": { 52 | //color: theme.colors.blue[8], 53 | background: "rgba(14, 44, 100, 0.86)", 54 | }, 55 | //color: theme.colors.gray[1], 56 | }, 57 | })} 58 | /> 59 | {data.amende_id === "custom" && ( 60 | <> 61 | setData({...data, amende_label: e.currentTarget.value})/* onChanged("amende_label", value)*/} 66 | /> 67 | { 74 | setData({...data, 75 | price: value, 76 | amount: data.remise ? MathRound(Number(value) - (Number(value) * data.remise / 100), 0) : value 77 | }) 78 | }/* onChanged("price", value)*/} 79 | /> 80 | 81 | )} 82 | 83 | ); 84 | }; 85 | -------------------------------------------------------------------------------- /web/src/animation/notifications.ts: -------------------------------------------------------------------------------- 1 | import * as Animation from './keyframes/notifcation'; 2 | 3 | export const SelectAnime = (enterAnim:string|undefined, exitAnime: string|undefined, enterPos: string, exitPos: string, enterFrom: string|undefined, exitTo: string|undefined) => { 4 | let posEnter, posExit; 5 | switch (enterPos) { 6 | case 'top': 7 | posEnter = Animation.Top[!enterAnim ? 'slideInElliptic' : enterAnim].enter; 8 | switch (enterFrom) { 9 | case 'top': 10 | posEnter = Animation.Top[!enterAnim ? 'slide' : enterAnim].enter; 11 | break; 12 | case 'left': 13 | posEnter = Animation.Left[!enterAnim ? 'slide' : enterAnim].enter; 14 | break; 15 | case 'right': 16 | posEnter = Animation.Right[!enterAnim ? 'slide' : enterAnim].enter; 17 | break; 18 | default: 19 | posEnter = Animation.Top[!enterAnim ? 'slideInElliptic' : enterAnim].enter; 20 | break; 21 | } 22 | break; 23 | case 'bottom': 24 | posEnter = Animation.Bottom[!enterAnim ? 'slide' : enterAnim].enter; 25 | break; 26 | case 'left': 27 | posEnter = Animation.Left[!enterAnim ? 'slide' : enterAnim].enter; 28 | break; 29 | case 'right': 30 | posEnter = Animation.Right[!enterAnim ? 'slide' : enterAnim].enter; 31 | break; 32 | default: 33 | posEnter = Animation.Top['slide'].enter; 34 | break; 35 | } 36 | 37 | switch (exitPos) { 38 | case 'top': 39 | posExit = Animation.Top[!exitAnime ? 'slideInElliptic' : exitAnime].exit; 40 | switch (exitTo) { 41 | case 'top': 42 | 43 | break; 44 | 45 | case 'left': 46 | 47 | break; 48 | case 'right': 49 | posExit = Animation.Right[!exitAnime ? 'slide' : exitAnime].exit 50 | break; 51 | default: 52 | 53 | break; 54 | } 55 | break; 56 | case 'bottom': 57 | posExit = Animation.Bottom[!exitAnime ? 'slide' : exitAnime].exit; 58 | break; 59 | case 'left': 60 | posExit = Animation.Left[!exitAnime ? 'slide' : exitAnime].exit; 61 | break; 62 | case 'right': 63 | posExit = Animation.Right[!exitAnime ? 'slide' : exitAnime].exit; 64 | break; 65 | default: 66 | posExit = Animation.Top['slideInElliptic'].exit; 67 | break; 68 | } 69 | 70 | return {posEnter, posExit}; 71 | } -------------------------------------------------------------------------------- /web/src/dev/debug/modals/custom.ts: -------------------------------------------------------------------------------- 1 | import { debugData } from '../../../utils/debugData'; 2 | import type { ModalPropsCustom/*, Option */} from '../../../typings'; 3 | const modalOptions = [ 4 | { type: 'input', label: 'Nom', required: true, default: 'Test', callback: true}, 5 | { type: 'input', label: 'Prénom', required: true, default: 'Test2', callback: true}, 6 | { type: 'select', label: 'Sexe', options: [{value: 'homme', label: 'Homme'}, {value: 'femme', label: 'Femme'}], required: true, default: 'homme', callback: true}, 7 | { type: 'number', label: 'Age', required: true, default: 10, callback: true }, 8 | { type: 'select', label: 'Ville', required: true, options: [{value: 'paris', label: 'Paris'}, {value: 'lyon', label: 'Lyon'}], default: 'lyon', callback: true }, 9 | { type: 'number', label: 'Argent', required: true, default: 10000000, format: {}, callback: true}, 10 | { type: 'multiselect', label: 'Multiselector', required: true, options: [{value: 'paris', label: 'Paris'}, {value: 'lyon', label: 'Lyon'}], default: ['lyon'], callback: true }, 11 | { type: 'colorpicker', label: 'Mon color picker', format: 'rgba', callback: true } 12 | 13 | /*{ type: 'input', label: 'Input Field', required: true, callback: true, error: 'Message perso', default: 'test' }, 14 | { type: 'number', label: 'Textarea Field', required: true, callback: true, default: 10 }, 15 | { type: 'select', label: 'Select Field', options: 16 | [ 17 | { value: 'react', label: 'React' }, 18 | { value: 'ng', label: 'Angular' }, 19 | { value: 'svelte', label: 'Svelte' }, 20 | { value: 'vue', label: 'Vue' } 21 | ], required: true, error: 'Select an option', callback: true, default: 'ng' }, 22 | /*{ 23 | type: 'checkbox', 24 | label: 'Checkbox Field', 25 | callback: true, 26 | }, 27 | { 28 | type: 'password', 29 | label: 'Password Field', 30 | required: true, 31 | }, 32 | /*{ 33 | type: 'slider', 34 | label: 'Slider Field', 35 | /*min: 120, 36 | max: 240, 37 | default: 180,*/ 38 | /*transition: { 39 | name: 'skew-up', 40 | duration: 100, 41 | timingFunction: 'ease-in-out' 42 | } 43 | }, 44 | { 45 | type: 'date', 46 | label: 'Date Input Field', 47 | required: true 48 | },*/ 49 | 50 | ]; 51 | 52 | export const debugModalsCustom = () => { 53 | debugData([ 54 | { 55 | action: 'supv:modal:opened-custom', 56 | data: { 57 | title: 'Title of the modal', 58 | useCallback: true, 59 | //canCancel: false, 60 | transition: { 61 | name: 'skew-up', 62 | duration: 200, 63 | timingFunction: 'ease-in-out' 64 | }, 65 | //canCancel: false, 66 | options: modalOptions, 67 | } as ModalPropsCustom, 68 | }, 69 | ]); 70 | }; 71 | -------------------------------------------------------------------------------- /web/src/features/resource/components/_badge.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | TextInput, 4 | ActionIcon, 5 | Group, 6 | Box, 7 | Badge, 8 | rem, 9 | NavLink, 10 | } from "@mantine/core"; 11 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 12 | import { faXmark } from "@fortawesome/free-solid-svg-icons"; 13 | import { ButtonsEditor } from "./_buttons"; 14 | import { _BadgeEditorProps } from "../../../typings"; 15 | 16 | export const BadgeEdit: React.FC<_BadgeEditorProps> = ({ 17 | inputKey, 18 | label, 19 | description, 20 | placeholder, 21 | defaultValue, 22 | resource, 23 | file, 24 | navKey, 25 | index, 26 | setResourceData 27 | }) => { 28 | const [isHovered, setIsHovered] = useState(0); 29 | const [isDisabled, setIsDisabled] = useState(true); 30 | const [value, setValue] = useState>(defaultValue || []); 31 | 32 | const RemoveBadge = (index: number) => { 33 | return ( 34 | { 40 | !isDisabled && setValue(value.filter((_, i) => i !== index)); 41 | }} 42 | onMouseEnter={() => setIsHovered(index + 10)} 43 | onMouseLeave={() => setIsHovered(0)} 44 | > 45 | 50 | 51 | ); 52 | }; 53 | 54 | return ( 55 | 63 | { 71 | if (e.key === "Enter") { 72 | !isDisabled && setValue([...value, e.currentTarget.value]); 73 | e.currentTarget.value = ""; 74 | } 75 | }} 76 | /> 77 | 78 | 79 | {value.map((badge, index) => ( 80 | 89 | {badge} 90 | 91 | ))} 92 | 93 | 94 | 105 | 106 | ); 107 | }; 108 | -------------------------------------------------------------------------------- /web/src/typings/Notification.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from 'react'; 2 | import { IconProp } from '@fortawesome/fontawesome-svg-core'; 3 | import { MantineColor, MantineGradient, MantineSize } from '@mantine/styles'; 4 | import { FontAwesomeIconProps } from '@fortawesome/react-fontawesome'; 5 | 6 | type IconStyle = { 7 | animation?: string; 8 | color?: CSSProperties; 9 | style?: CSSProperties; 10 | } 11 | 12 | export interface BadgeProps { 13 | type?: 'avatar' | 'icon' 14 | name?: IconProp; 15 | src?: string; 16 | alt?: string; 17 | color?: MantineColor | FontAwesomeIconProps['color']; 18 | size?: MantineSize | FontAwesomeIconProps['size']; 19 | style?: CSSProperties; 20 | animation?: 'beat' | 'fade' | 'flip' | 'spin' | 'pulse' | 'shake'; 21 | text: string; 22 | radius?: MantineSize; 23 | variant?: 'filled' | 'outline' | 'light' | 'dot'; 24 | position?: 'left' | 'right'; 25 | mr?: number; 26 | ml?: number; 27 | mt?: number; 28 | mb?: number; 29 | m?: number; 30 | colorIcon?: MantineColor | FontAwesomeIconProps['color']; 31 | imageProps?: Record; 32 | gradient?: MantineGradient; 33 | } 34 | 35 | export interface NotificationItemsProps { 36 | dur: number; 37 | id: string; 38 | visible?: boolean; 39 | data: NotificationProps; 40 | } 41 | 42 | export interface NotificationProps { 43 | image?: string; 44 | title?: string; 45 | id?: string; 46 | key?: number | string; 47 | description?: string; 48 | dur?: number | 3000; // default: 3000 49 | type?: string; 50 | duration?: number | 3000; // default: 3000 51 | icon?: IconProp; // default: check on type 52 | color?: string; // default: check on type 53 | closable?: boolean; // default: false (not semi-implementation) 54 | border?: boolean; // default: false (not semi-implementation) 55 | iconStyle?: IconStyle; 56 | style?: CSSProperties; 57 | animation?: Animation; 58 | badge?: BadgeProps; 59 | } 60 | 61 | export interface Minimap { 62 | x: number; 63 | y: number; 64 | w: number; 65 | h: number; 66 | expanded: boolean; 67 | } 68 | 69 | export interface MinimapProps { 70 | name: 'x' | 'y' | 'w' | 'h' | 'expanded'; 71 | value: number | boolean; 72 | } 73 | 74 | /*export type BadgeIconProps = { 75 | name: IconProp; 76 | color?: FontAwesomeIconProps['color']; 77 | size?: FontAwesomeIconProps['size']; 78 | style?: CSSProperties; 79 | animation?: 'beat' | 'fade' | 'flip' | 'spin' | 'pulse' | 'shake'; 80 | } 81 | 82 | export type BadgeAvatarProps = { 83 | src: string; 84 | alt?: string; 85 | mr?: number; 86 | ml: number; 87 | mt?: number; 88 | mb?: number; 89 | m?: number; 90 | size?: MantineSize; 91 | variant?: 'outline' | 'light' | 'filled' | 'gradient' 92 | imageProps: Record, 93 | radius?: MantineSize; 94 | gradient?: MantineGradient; 95 | } 96 | 97 | export interface BadgeSectionPropsAvatar { 98 | data: BadgeAvatarProps 99 | } 100 | 101 | export interface BadgeSectionPropsIcon { 102 | data: BadgeIconProps 103 | } 104 | 105 | type Animation = { 106 | enter: string; 107 | exit: string; 108 | } 109 | 110 | */ -------------------------------------------------------------------------------- /modules/nui/client/modals.lua: -------------------------------------------------------------------------------- 1 | ---@type fun(index: number, value: any): void | nil 2 | local onCallback 3 | local p 4 | 5 | ---@class DataConfirmProps 6 | ---@field title? string 7 | ---@field description? string 8 | ---@field transition? { name: string, duration: number, timingFunction: string } ref: https://mantine.dev/core/transition/ 9 | 10 | ---@class ModalConfirm 11 | ---@field data? DataConfirmProps 12 | 13 | ---@class DataCustomProps 14 | ---@field title string 15 | ---@field options { type: 'checkbox' | 'input' | 'select' | 'date' | 'password' | 'number', label: string } 16 | ---@field canCancel? boolean 17 | ---@field transition? { name: string, duration: number, timingFunction: string } ref: https://mantine.dev/core/transition/ 18 | 19 | ---@class ModalCustom 20 | ---@field data DataCustomProps 21 | ---@field callback fun(index: number, value: any): void 22 | 23 | ---@param title string 24 | ---@param data DataCustomProps 25 | ---@param callback fun(index: number, value: any): void 26 | ---@return table|nil 27 | ---@type ModalCustom 28 | local function Custom(data, callback) 29 | if type(data) ~= 'table' then return end 30 | if type(data.title) ~= 'string' then return end 31 | onCallback = callback 32 | 33 | supv.sendReactMessage(true, { 34 | action = 'supv:modal:opened-custom', 35 | data = { 36 | title = data.title, 37 | canCancel = data.canCancel, 38 | transition = data.transition, 39 | options = data.options, 40 | useCallback = callback and true or false 41 | } 42 | }, { 43 | focus = true, 44 | locations = 'center' 45 | }) 46 | 47 | p = promise.new() 48 | return supv.await(p) 49 | end 50 | 51 | ---@param data DataConfirmProps 52 | ---@return boolean 53 | ---@type ModalConfirm 54 | local function Confirm(data) 55 | if type(data) ~= 'table' then return end 56 | 57 | supv.sendReactMessage(true, { 58 | action = 'supv:modal:opened-confirm', 59 | data = data 60 | }, { 61 | focus = true, 62 | locations = 'center' 63 | }) 64 | 65 | p = promise.new() 66 | return supv.await(p) 67 | end 68 | 69 | ---@param types custom | confirm as string 70 | ---@param data table 71 | ---@param callback fun(index: number, value: any) 72 | ---@return table|boolean|nil 73 | function supv.openModal(types, data, callback) 74 | if p or type(types) ~= 'string' then return end 75 | 76 | if types == 'custom' then 77 | return Custom(data, callback) 78 | elseif types == 'confirm' then 79 | return Confirm(data) 80 | end 81 | end 82 | 83 | ---@param data boolean 84 | supv.registerReactCallback('supv:modal:confirm', function(data, cb) 85 | if p then p:resolve(data) end p = nil 86 | cb(1) 87 | end, true) 88 | 89 | ---@param data { index: number, value: any } 90 | supv.registerReactCallback('supv:modal:submit', function(data, cb) 91 | cb(1) 92 | if p then p:resolve(data) end p, onCallback = nil, nil 93 | end, true) 94 | 95 | ---@param data { index: number, value: any } 96 | supv.registerReactCallback('supv:modal:callback', function(data, cb) 97 | cb(1) 98 | onCallback(data.index, data.value) 99 | end) -------------------------------------------------------------------------------- /imports/locale/shared.lua: -------------------------------------------------------------------------------- 1 | local data = {} ---@type { [string]: string} 2 | 3 | ---@param str string 4 | ---@param ... any 5 | ---@return string 6 | function translate(str, ...) 7 | local _t = data[str] 8 | if _t then 9 | if ... then 10 | return _t:format(...) 11 | end 12 | 13 | return _t 14 | end 15 | return str 16 | end 17 | 18 | --- supv.locale.init 19 | local function Initialize() 20 | local env = supv.json.load(('locales/%s'):format(supv.lang)) 21 | if not env then 22 | return warn(("Impossible de chargé locales/%s.json dans l'environnement : %s"):format(supv.lang, supv.env)) 23 | end 24 | 25 | for k, v in pairs(env) do 26 | if not data[k] then 27 | if type(v) == 'string' then 28 | for var in v:gmatch('${[%w%s%p]-}') do -- credit: ox_lib 29 | local locale = env[var:sub(3, -2)] 30 | 31 | if locale then 32 | locale = locale:gsub('%%', '%%%%') 33 | v = v:gsub(var, locale) 34 | end 35 | end 36 | end 37 | data[k] = v 38 | end 39 | end 40 | end 41 | 42 | --- supv.locale.get 43 | ---@param key? string 44 | ---@return table|string 45 | local function GetInitialized(key) 46 | return data[key] or data 47 | end 48 | 49 | --- supv.locale.load ( example : supv.locale.load('es_extended', 'locales.en', 'lua') | supv.locale.load('supv_core', 'locales.en', 'json') ) 50 | ---@param resource string 51 | ---@param path string 52 | ---@param ext string 53 | local function LoadExternal(resource, path, ext) 54 | local filePath, folder, result = path, resource 55 | filePath = filePath:gsub('%.', '/') 56 | 57 | if ext == 'lua' then 58 | local import = LoadResourceFile(folder, filePath..'.lua') 59 | local func, err = load(import, ('@@%s/%s.lua'):format(folder, filePath)) 60 | if not func or err then 61 | return error(err or ("unable to load module '%s/%s.lua'"):format(folder, filePath), 3) 62 | end 63 | 64 | result = func() 65 | elseif ext == 'json' then 66 | filePath = filePath:gsub('%.json', '') 67 | result = supv.json.load(filePath, folder) 68 | end 69 | 70 | if type(result) ~= 'table' then 71 | return error(("Le fichier '%s/%s' ne retourne pas une table mais (%s)"):format(folder, filePath, type(result)), 2) 72 | end 73 | 74 | for k, v in pairs(result) do 75 | if not data[k] then 76 | if type(v) == 'string' then 77 | for var in v:gmatch('${[%w%s%p]-}') do -- credit: ox_lib 78 | local locale = result[var:sub(3, -2)] 79 | 80 | if locale then 81 | locale = locale:gsub('%%', '%%%%') 82 | v = v:gsub(var, locale) 83 | end 84 | end 85 | end 86 | 87 | data[k] = v 88 | end 89 | end 90 | end 91 | 92 | return { 93 | init = Initialize, 94 | get = GetInitialized, 95 | load = LoadExternal, 96 | } -------------------------------------------------------------------------------- /imports/framework/qb/server.lua: -------------------------------------------------------------------------------- 1 | local QBCore = exports["qb-core"]:GetCoreObject() 2 | local Player, method = {}, {} 3 | 4 | function Player:__index(index) 5 | local value = method[index] 6 | 7 | if value then 8 | return function(...) 9 | return value(self, ...) 10 | end 11 | end 12 | 13 | return self[index] 14 | end 15 | 16 | function method.GetGang(player) 17 | return player.PlayerData.gang 18 | end 19 | 20 | function method.SetGang(player, key, value) 21 | player.Functions.SetGang(key, value) 22 | end 23 | 24 | function method.GetJob(player) 25 | return player.PlayerData.job 26 | end 27 | 28 | function method.SetJob(player, key, value) 29 | player.Functions.SetJob(key, value) 30 | end 31 | 32 | function method.Flush() 33 | return nil, collectgarbage() 34 | end 35 | 36 | local function GetPlayerFromId(source) 37 | local player = QBCore.Functions.GetPlayer(source) 38 | return player and setmetatable(player, Player) 39 | end 40 | 41 | return { 42 | GetPlayerFromId = GetPlayerFromId 43 | } 44 | 45 | --[[ 46 | local Framework = {} 47 | 48 | function Framework.GetPlayerFromdId(source) 49 | local player = QBCore.Functions.GetPlayer(source) 50 | return player 51 | end 52 | 53 | function Framework.GetPlayers(key, value) 54 | local players = QBCore.Functions.GetQBPlayers() 55 | if key and value then 56 | local filter = {} 57 | for _, v in pairs(players) 58 | if v.PlayerData[key] == value then 59 | filter[#filter + 1] = v 60 | end 61 | end 62 | return filter 63 | end 64 | return players 65 | end 66 | 67 | function Framework.GetPlayerData(player) 68 | return player.PlayerData 69 | end 70 | 71 | function Framework.GetJob(player) 72 | return player.PlayerData.job 73 | end 74 | 75 | function Framework.GetGang(player) 76 | return player.PlayerData.gang 77 | end 78 | 79 | function Framework.SetJob(player, job, grade) 80 | player.Functions.SetJob(job, grade) 81 | end 82 | 83 | function Framework.SetGang(player, gang, grade) 84 | player.Functions.SetGang(gang, grade) 85 | end 86 | 87 | return Framework 88 | --]] 89 | --[[ 90 | local player, method = {}, {} 91 | 92 | function method.getGang(obj) 93 | return obj.PlayerData.gang 94 | end 95 | 96 | function method.setGang(obj, key, value) 97 | obj.Functions.SetGang(key, value) 98 | end 99 | 100 | function method.getJob(obj) 101 | return obj.PlayerData.job 102 | end 103 | 104 | function method.setJob(obj, key, value) 105 | obj.Functions.SetJob(key, value) 106 | end 107 | 108 | function method.flush() 109 | return nil, collectgarbage() 110 | end 111 | 112 | function player:__index(index) 113 | local value = method[index] 114 | 115 | if value then 116 | return function(...) 117 | return value(self, ...) 118 | end 119 | end 120 | 121 | return self[index] 122 | end 123 | 124 | local function GetPlayerFromId(source) 125 | local p = QBCore.Functions.GetPlayer(source) 126 | return p and setmetatable(p, player) 127 | end 128 | 129 | return { 130 | GetPlayerFromId = GetPlayerFromId 131 | } 132 | 133 | ]] -------------------------------------------------------------------------------- /web/src/animation/keyframes/notifcation.ts: -------------------------------------------------------------------------------- 1 | import { keyframes } from '@mantine/core' 2 | 3 | type Animation = { 4 | [key: string]: { 5 | enter: any; 6 | exit: any; 7 | } 8 | } 9 | 10 | export const Top: Animation = { 11 | slide: { 12 | enter: keyframes({ 13 | from: { 14 | opacity: 0, 15 | transform: 'translateY(-30px)', 16 | }, 17 | to: { 18 | opacity: 1, 19 | transform: 'translateY(0px)', 20 | }, 21 | }), 22 | exit: keyframes({ 23 | from: { 24 | opacity: 1, 25 | transform: 'translateY(0px)', 26 | }, 27 | to: { 28 | opacity: 0, 29 | transform: 'translateY(-100%)', 30 | }, 31 | }), 32 | }, 33 | slideInElliptic: { 34 | enter: keyframes({ 35 | from: { 36 | transform: 'translateY(-600px) rotateX(-30deg) scale(0)', 37 | transformOrigin: '50% 100%', 38 | opacity: 0, 39 | }, 40 | to: { 41 | transform: 'translateY(0) rotateX(0) scale(1)', 42 | transformOrigin: '50% 1400px', 43 | opacity: 1, 44 | }, 45 | }), 46 | exit: keyframes({ 47 | from: { 48 | transform: 'translateY(0) rotateX(0) scale(1)', 49 | transformOrigin: '50% 1400px', 50 | opacity: 1, 51 | }, 52 | to: { 53 | transform: 'translateY(-600px) rotateX(-30deg) scale(0)', 54 | transformOrigin: '50% 100%', 55 | opacity: 0, 56 | }, 57 | }) 58 | 59 | }, 60 | } 61 | 62 | export const Bottom: Animation = { 63 | slide: { 64 | enter: keyframes({ 65 | from: { 66 | opacity: 0, 67 | transform: 'translateY(30px)', 68 | }, 69 | to: { 70 | opacity: 1, 71 | transform: 'translateY(0px)', 72 | }, 73 | }), 74 | exit: keyframes({ 75 | from: { 76 | opacity: 1, 77 | transform: 'translateY(0px)', 78 | }, 79 | to: { 80 | opacity: 0, 81 | transform: 'translateY(100%)', 82 | }, 83 | }) 84 | } 85 | } 86 | 87 | export const Left: Animation = { 88 | slide: { 89 | enter: undefined, 90 | exit: keyframes({ 91 | from: { 92 | opacity: 1, 93 | transform: 'translateX(0px)', 94 | }, 95 | to: { 96 | opacity: 0, 97 | transform: 'translateX(-100%)', 98 | }, 99 | }) 100 | } 101 | } 102 | 103 | export const Right: Animation = { 104 | slide: { 105 | enter: undefined, 106 | exit: keyframes({ 107 | from: { 108 | opacity: 1, 109 | transform: 'translateX(0px)', 110 | }, 111 | to: { 112 | opacity: 0, 113 | transform: 'translateX(100%)', 114 | }, 115 | }) 116 | } 117 | } -------------------------------------------------------------------------------- /web/src/features/modal/ModalConfirm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Modal, Divider, Group, Text, Stack } from '@mantine/core'; 3 | import { useDisclosure } from '@mantine/hooks'; 4 | import { faCheck, faXmark } from '@fortawesome/free-solid-svg-icons'; 5 | import { useNuiEvent } from '../../hooks/useNuiEvent'; 6 | import { fetchNui } from '../../utils/fetchNui'; 7 | import ReactMarkdown from 'react-markdown'; 8 | import remarkGfm from 'remark-gfm'; 9 | import AnimatedButton from './components/buttons'; 10 | import type { ModalConfirmProps } from '../../typings'; 11 | 12 | const ModalConfirm: React.FC = () => { 13 | const [values, setValues] = useState({}); 14 | const [opened, { close, open }] = useDisclosure(false); 15 | 16 | const handleConfirm = async (value: boolean) => { 17 | close(); 18 | await new Promise((resolve) => setTimeout(resolve, 200)); 19 | fetchNui('supv:modal:confirm', value); 20 | }; 21 | 22 | useNuiEvent('supv:modal:opened-confirm', async (data) => { 23 | if (!data.title && !data.description) return; 24 | setValues(data); 25 | await new Promise((resolve) => setTimeout(resolve, 200)); 26 | open(); 27 | }); 28 | 29 | return ( 30 | <> 31 | 51 | 52 | 53 | {values.description && values.title ? ( 54 | 58 | ) : ( 59 | 66 | {values.description} 67 | 68 | )} 69 | 70 | 77 | 84 | 85 | 86 | 87 | 88 | ); 89 | }; 90 | 91 | export default ModalConfirm; 92 | -------------------------------------------------------------------------------- /imports/request/client.lua: -------------------------------------------------------------------------------- 1 | local Request = {} 2 | Request.__index = Request 3 | 4 | -- Fonction générique pour gérer les requêtes 5 | ---@param _type string<'animDict' | 'model' | 'animSet' | 'ptfx' | 'weaponAsset' | 'scalformMovie' | 'textureDict'> 6 | ---@param request function 7 | ---@param hasLoaded function 8 | ---@param name string 9 | ---@param timeout? number 10 | ---@param ... unknown 11 | ---@return any 12 | function Request:_request(_type, request, hasLoaded, name, timeout, ...) 13 | if hasLoaded(name) then return name end 14 | 15 | request(name, ...) 16 | 17 | return supv.waitFor(function() 18 | if hasLoaded(name) then return name end 19 | end, timeout or 10000, ('Failed to load %s : %s'):format(_type, name)) 20 | end 21 | 22 | -- Définition des méthodes spécifiques pour chaque type de requête 23 | function Request:model(name, timeout) 24 | if not IsModelValid(name) then 25 | error(("attempted to load invalid model '%s'"):format(name), 2) 26 | end 27 | return self:_request('model', RequestModel, HasModelLoaded, name, timeout) 28 | end 29 | 30 | function Request:animDict(name, timeout) 31 | if not DoesAnimDictExist(name) then 32 | error(("attempted to load invalid animDict '%s'"):format(name), 2) 33 | end 34 | return self:_request('animDict', RequestAnimDict, HasAnimDictLoaded, name, timeout) 35 | end 36 | 37 | function Request:animSet(name, timeout) 38 | if type(name) ~= 'string' then 39 | error(("attempted to load invalid animSet '%s'"):format(name), 2) 40 | end 41 | return self:_request('animSet', RequestAnimSet, HasAnimSetLoaded, name, timeout) 42 | end 43 | 44 | function Request:ptfx(name, timeout) 45 | return self:_request('ptfx', RequestNamedPtfxAsset, HasNamedPtfxAssetLoaded, name, timeout) 46 | end 47 | 48 | function Request:weaponAsset(weaponType, weaponFlags, extraFlags, timeout) 49 | if HasWeaponAssetLoaded(weaponType) then return weaponType end 50 | 51 | local weaponTypeStr = type(weaponType) 52 | if weaponTypeStr ~= 'string' and weaponTypeStr ~= 'number' then 53 | error(("expected weaponType to have type 'string' or 'number' (received %s)"):format(weaponTypeStr), 2) 54 | end 55 | 56 | if weaponFlags and type(weaponFlags) ~= 'number' then 57 | error(("expected weaponResourceFlags to have type 'number' (received %s)"):format(type(weaponFlags)), 2) 58 | end 59 | 60 | if extraFlags and type(extraFlags) ~= 'number' then 61 | error(("expected extraWeaponComponentFlags to have type 'number' (received %s)"):format(type(extraFlags)), 2) 62 | end 63 | 64 | return self:_request('weaponAsset', RequestWeaponAsset, HasWeaponAssetLoaded, weaponType, timeout, weaponFlags, extraFlags) 65 | end 66 | 67 | function Request:scalformMovie(scaleformName, timeout) 68 | if HasScaleformMovieLoaded(scaleformName) then return scaleformName end 69 | 70 | local scalform = RequestScaleformMovie(scaleformName) 71 | 72 | return supv.waitFor(function() 73 | if HasScaleformMovieLoaded(scalform) then return scalform end 74 | end, timeout or 10000, ('Failed to load scaleformMovie : %s'):format(scaleformName)) 75 | end 76 | 77 | function Request:textureDict(name, timeout) 78 | if type(name) ~= 'string' then 79 | error(("expected textureDict to have type 'string' (received %s)"):format(type(name)), 2) 80 | end 81 | return self:_request('textureDict', RequestStreamedTextureDict, HasStreamedTextureDictLoaded, name, timeout) 82 | end 83 | 84 | supv.request = setmetatable({}, Request) 85 | 86 | return supv.request -------------------------------------------------------------------------------- /modules/handlers/client/events.lua: -------------------------------------------------------------------------------- 1 | --local RegisterNetEvent , AddEventHandler , TriggerEvent , TriggerServerEvent , joaat = RegisterNetEvent, AddEventHandler, TriggerEvent, TriggerServerEvent, joaat 2 | local timers, GetGameTimer = {}, GetGameTimer 3 | 4 | ---@param name string 5 | ---@return table|false 6 | ---@private 7 | local function IsEventCooldown(name) 8 | if timers[name] then 9 | return timers[name] 10 | end 11 | return false 12 | end 13 | 14 | ---@return boolean 15 | ---@private 16 | local function GetCooldown(self) 17 | if not self then return end 18 | local time = GetGameTimer() 19 | if (time - self.time) < self.cooldown then 20 | return true 21 | end 22 | self.time = time 23 | return false 24 | end 25 | 26 | ---@param name string 27 | ---@param timer number 28 | ---@private 29 | local function RegisterCooldown(name, timer) 30 | local self = {} 31 | 32 | self.time = GetGameTimer() 33 | self.cooldown = timer 34 | self.onCooldown = GetCooldown 35 | 36 | timers[name] = self 37 | end 38 | 39 | function supv.RegisterEventCooldown(name, timer) 40 | return RegisterCooldown(name, timer) 41 | end 42 | 43 | function supv.IsEventCooldown(name) 44 | return IsEventCooldown(name) 45 | end 46 | 47 | --[[ 48 | ---@param name string 49 | ---@param cb fun(...: any) 50 | ---@param cooldown? number 51 | ---@public AddEventHandler 52 | function supv:on(name, cb, cooldown) 53 | if type(name) ~= 'string' then return end 54 | if cb and (type(cb) ~= 'table' and type(cb) ~= 'function') then return end 55 | 56 | if type(cooldown) == 'number' then 57 | RegisterCooldown(name, cooldown) 58 | end 59 | 60 | local function EventHandler(...) 61 | local eventCooldown = IsEventCooldown(name) 62 | if eventCooldown and eventCooldown:onCooldown() then 63 | return warn('Ignoring event', name, 'because of cooldown'..'\n') 64 | end 65 | cb(...) 66 | end 67 | 68 | return AddEventHandler(self:hashEvent(name), EventHandler) 69 | end 70 | 71 | ---@param name string 72 | ---@param cb? fun(...: any) 73 | ---@param cooldown? number 74 | ---@public RegisterNetEvent 75 | function supv:onNet(name, cb, cooldown) 76 | if type(name) ~= 'string' then return end 77 | if cb and (type(cb) ~= 'table' and type(cb) ~= 'function') then return RegisterNetEvent(self:hashEvent(name)) end 78 | 79 | if type(cooldown) == 'number' then 80 | RegisterCooldown(name, cooldown) 81 | end 82 | 83 | local function EventHandler(...) 84 | local eventCooldown = IsEventCooldown(name) 85 | if eventCooldown and eventCooldown:onCooldown() then 86 | return warn('Ignoring event', name, 'because of cooldown'..'\n') 87 | end 88 | cb(...) 89 | end 90 | 91 | return RegisterNetEvent(self:hashEvent(name), EventHandler) 92 | end 93 | 94 | ---@param name string 95 | ---@param ... any 96 | ---@public TriggerServerEvent 97 | function supv:emitNet(name, ...) 98 | if not tokenClient then tokenClient = callback.sync(joaat('token')) end 99 | if type(name) ~= 'string' then return end 100 | TriggerServerEvent(self:hashEvent(name, 'server'), tokenClient, ...) 101 | end 102 | 103 | ---@param name string 104 | ---@param ... any 105 | ---@public TriggerEvent 106 | function supv:emit(name, ...) 107 | if type(name) ~= 'string' then return end 108 | TriggerEvent(self:hashEvent(name), ...) 109 | end 110 | ]] -------------------------------------------------------------------------------- /modules/handlers/client/nui.lua: -------------------------------------------------------------------------------- 1 | ---@alias CursorPositionProps 'top-right' | 'bottom-right' 2 | 3 | ---@class SendReactValue 4 | ---@field action string 5 | ---@field data? any 6 | ---@type table 7 | 8 | ---@class SendReactOptions 9 | ---@field focus? boolean | {[1]: boolean, [2]: boolean} 10 | ---@field locations? CursorPositionProps | {x: float, y: float} 11 | ---@field keepInput? boolean 12 | 13 | local SetCursorLocation , SetNuiFocusKeepInput, SendNUIMessage , type , SetNuiFocus , table , RegisterNUICallback , IsNuiFocused = SetCursorLocation, SetNuiFocusKeepInput, SendNUIMessage, type, SetNuiFocus, table, RegisterNUICallback, IsNuiFocused 14 | 15 | ---@type table 16 | local cusorPosition = { 17 | ['top-right'] = {x = 0.90, y = 0.10}, 18 | ['bottom-right'] = {x = 0.90, y = 0.90}, 19 | ['center'] = {x = 0.50, y = 0.50} 20 | } 21 | 22 | --- supv.sendReactMessage 23 | ---@param visible? boolean 24 | ---@param value? SendReactValue 25 | ---@param options? SendReactOptions 26 | local function SendReactMessage(visible, value, options) 27 | --print(json.encode(value, {indent = true})) 28 | if type(visible) == 'boolean' then 29 | 30 | ---@todo reset focus options when visible is false and focus active = true 31 | if visible == false and IsNuiFocused() then 32 | SetNuiFocus(supv.notifyQueue(), false) 33 | SetNuiFocusKeepInput(supv.notifyQueue()) 34 | end 35 | end 36 | 37 | if type(value) == 'table' and type(value.action) == 'string' then 38 | SendNUIMessage({ 39 | action = value.action, 40 | data = value.data, 41 | }) 42 | end 43 | 44 | if options then 45 | if type(options.focus) == 'boolean' then 46 | SetNuiFocus(options.focus, options.focus) 47 | elseif type(options.focus) == 'table' and table.type(options.focus) == 'array' then 48 | SetNuiFocus(options.focus[1], options.focus[2]) 49 | elseif type(options.focus) == 'string' and options.focus == 'ignore' then 50 | goto ignore 51 | end 52 | 53 | if type(options.locations) == 'string' then 54 | SetCursorLocation(cusorPosition[options.locations].x, cusorPosition[options.locations].y) 55 | elseif type(options.locations) == 'table' then 56 | SetCursorLocation(options.locations[1] or options.locations.x, options.locations[2] or options.locations.y) 57 | end 58 | 59 | if type(options.keepInput) == 'boolean' then 60 | SetNuiFocusKeepInput(options.keepInput) 61 | end 62 | 63 | ---@todo Need more options about focus options? 64 | ::ignore:: 65 | end 66 | end 67 | 68 | --- supv.registerReactCallback 69 | ---@param name string 70 | ---@param cb fun(data: any, cb: fun(...: any)) 71 | ---@param visible? boolean 72 | local function RegisterReactCallback(name, cb, visible) 73 | RegisterNUICallback(name, function(...) 74 | if visible then 75 | SendReactMessage(false) 76 | end 77 | cb(...) 78 | end) 79 | end 80 | 81 | local function ResetFocus() 82 | SetNuiFocus(false, false) 83 | end 84 | 85 | RegisterNUICallback('supv:react:getConfig', function(data, cb) 86 | --print(json.encode(data, {indent = true}), 'data') 87 | cb(1) 88 | end) 89 | 90 | supv.sendReactMessage = SendReactMessage 91 | supv.registerReactCallback = RegisterReactCallback 92 | supv.resetFocus = ResetFocus 93 | 94 | --[[ 95 | SetCursorLocation(0.90, 0.90) bottom-right 96 | SetNuiFocus(true, true) 97 | SetNuiFocusKeepInput(true) 98 | --]] -------------------------------------------------------------------------------- /web/src/features/resource/components/_canAdd.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TextInput, NumberInput, Modal, ActionIcon } from "@mantine/core"; 3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 4 | import { faPlus } from "@fortawesome/free-solid-svg-icons"; 5 | import { _CanAddInputEditorProps } from "../../../typings"; 6 | 7 | export const CanAddInputEditor: React.FC<_CanAddInputEditorProps> = ({ 8 | addOption, 9 | state, 10 | value, 11 | setValue, 12 | }) => { 13 | 14 | const HandleModal = () => { 15 | return ( 16 | { 19 | 20 | }} 21 | title={addOption.description} 22 | > 23 | { 25 | if (e.key === "Enter") { 26 | if (addOption.in === "object") { 27 | const newValue = { ...value }; 28 | const currentVal = e.currentTarget.value; 29 | switch (addOption.keyFormat) { 30 | case "lowercase": 31 | newValue[currentVal.toLowerCase()] = true; 32 | break; 33 | case "uppercase": 34 | newValue[currentVal.toUpperCase()] = true; 35 | break; 36 | default: 37 | newValue[currentVal] = true; 38 | break; 39 | } 40 | setValue(newValue); 41 | } 42 | } 43 | }} 44 | icon={ 45 | 46 | } 47 | m={10} 48 | size="xs" 49 | disabled={state} 50 | placeholder={addOption.placeholder} 51 | /> 52 | 53 | ) 54 | } 55 | 56 | return ( 57 | <> 58 | {addOption.type === "number" ? ( 59 | { 62 | if (e.key === "Enter") { 63 | if (addOption.in === "object") { 64 | setValue({ ...value, [e.currentTarget.value]: true }); 65 | } else { 66 | setValue([...value, e.currentTarget.value]); 67 | } 68 | } 69 | }} 70 | icon={ 71 | 72 | } 73 | m={10} 74 | size="xs" 75 | disabled={state} 76 | placeholder={addOption.placeholder} 77 | /> 78 | ) : addOption.type === 'object' ? ( 79 | { 81 | HandleModal() 82 | }} 83 | > 84 | 85 | 86 | ) : ( 87 | { 90 | if (e.key === "Enter") { 91 | if (addOption.in === "object") { 92 | const newValue = { ...value }; 93 | const currentVal = e.currentTarget.value; 94 | switch (addOption.keyFormat) { 95 | case "lowercase": 96 | newValue[currentVal.toLowerCase()] = true; 97 | break; 98 | case "uppercase": 99 | newValue[currentVal.toUpperCase()] = true; 100 | break; 101 | default: 102 | newValue[currentVal] = true; 103 | break; 104 | } 105 | setValue(newValue); 106 | } 107 | } 108 | }} 109 | icon={ 110 | 111 | } 112 | m={10} 113 | size="xs" 114 | disabled={state} 115 | placeholder={addOption.placeholder} 116 | /> 117 | )} 118 | 119 | ); 120 | }; -------------------------------------------------------------------------------- /modules/handlers/server/webhook.lua: -------------------------------------------------------------------------------- 1 | local PerformHttpRequest , config = PerformHttpRequest, require 'config.server.webhook' 2 | local toUpper = require('imports.string.shared').firstToUpper 3 | local on = require 'imports.on.server' 4 | 5 | string.to_utf8 = require('imports.utf8.shared').to_utf8 6 | assert(os.setlocale(config.localization)) 7 | 8 | ---@class WebhookDataProps 9 | ---@field bot_name? string 10 | ---@field avatar? string 11 | ---@field date_format? string 12 | ---@field footer_icon? string 13 | 14 | ---@class WebhookEmbedProps 15 | ---@field title? string 16 | ---@field description? string 17 | ---@field image? string 18 | ---@field color? integer 19 | 20 | ---@class WebhookMessageProps 21 | ---@field text string 22 | ---@field data WebhookDataProps 23 | 24 | --- supv:webhook('embed') 25 | ---@param url string 26 | ---@param embeds WebhookEmbedProps 27 | ---@param data WebhookDataProps 28 | local function embed(url, embeds, data, response) 29 | local date = { 30 | letter = ("\n%s %s"):format(toUpper(os.date("%A %d")), toUpper(os.date("%B %Y : [%H:%M:%S]"))):to_utf8(), 31 | numeric = ("\n%s"):format(os.date("[%d/%m/%Y] - [%H:%M:%S]")) 32 | } 33 | 34 | url = config.channel[url] or url 35 | --print(date, 'date') 36 | 37 | local _embed = { 38 | { 39 | ["color"] = data?.color or config.default.color, 40 | ["title"] = embeds.title or '', 41 | ["description"] = embeds.description or '', 42 | ["footer"] = { 43 | ["text"] = data?.date_format and date[data?.date_format] or config.default.date_format and date[config.default.date_format], 44 | ["icon_url"] = data?.footer_icon or config.default.foot_icon, 45 | }, 46 | ['image'] = embeds.image and { 47 | ['url'] = embeds.image 48 | } 49 | }, 50 | } 51 | 52 | local p = response and promise.new() 53 | PerformHttpRequest(url, function(err, text, headers) 54 | if response then 55 | if err ~= 200 then 56 | warn(("Error while sending webhook to %s"):format(url)) 57 | p:reject(err) 58 | end 59 | 60 | local resp = json.decode(text) 61 | p:resolve(resp) 62 | end 63 | end, 'POST', json.encode({ 64 | username = data?.bot_name or config.default.bot_name, 65 | embeds = _embed, 66 | avatar_url = data?.avatar or config.default.avatar, 67 | }), {['Content-Type'] = 'application/json'}) 68 | 69 | return response and supv.await(p) 70 | end 71 | 72 | --- supv:webhook('message') 73 | ---@param url string 74 | ---@param text string 75 | ---@param data WebhookDataProps.bot_name 76 | local function message(url, text, data) 77 | local c = config 78 | url = config.channel[url] or url 79 | 80 | PerformHttpRequest(url, function(err, text, headers) end, 'POST', json.encode({ 81 | username = data?.bot_name or config.default.bot_name, 82 | content = text 83 | }), {['Content-Type'] = 'application/json', ['charset'] = 'utf-8'}) 84 | end 85 | 86 | ---@param types string 87 | ---@param ... any 88 | ---@return void | promise 89 | local function SendWebhookDiscord(types, ...) 90 | if types == 'embed' then 91 | return embed(...) 92 | elseif types == 'message' then 93 | return message(...) 94 | end 95 | return error("Invalid types of webhook", 1) 96 | end 97 | 98 | supv.webhook = SendWebhookDiscord 99 | function supv.getWebhookChannel(name) 100 | return name == nil and config.channel or config.channel[name] 101 | end 102 | 103 | if config.playing_from ~= 'shared' then return end 104 | 105 | ---@todo need more implementation about webhook send from client 106 | on.net('webhook:received', function (source, types, ...) 107 | warn(("%s trying to playing webhook from client"):format(source)) 108 | supv.webhook(types, ...) 109 | end) -------------------------------------------------------------------------------- /web/src/features/chat/Reactions.tsx: -------------------------------------------------------------------------------- 1 | // import { useState } from 'react'; 2 | // import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 | // import { faLaugh, faHeart, faThumbsUp, faAngry } from '@fortawesome/free-regular-svg-icons'; 4 | 5 | // interface EmojiReactionProps { 6 | // reactions: { [key: string]: number }; 7 | // onReactionClick: (reaction: string, operation: number) => void; 8 | // } 9 | 10 | // const EmojiReaction: React.FC = ({ reactions, onReactionClick }) => { 11 | // const [hoveredReaction, setHoveredReaction] = useState(null); 12 | 13 | // const handleReactionClick = (reaction: string) => { 14 | // if (reactions[reaction]) { 15 | // onReactionClick(reaction, -1); 16 | // } else { 17 | // onReactionClick(reaction, 1); 18 | // } 19 | // }; 20 | 21 | // const handleMouseEnter = (reaction: string) => { 22 | // setHoveredReaction(reaction); 23 | // }; 24 | 25 | // const handleMouseLeave = () => { 26 | // setHoveredReaction(null); 27 | // }; 28 | 29 | // const getReactionCount = (reaction: string) => { 30 | // return reactions[reaction] || 0; 31 | // }; 32 | 33 | // const getColor = (reaction: string) => { 34 | // switch (reaction) { 35 | // case 'thumbs-up': 36 | // return reactions[reaction] || hoveredReaction === reaction ? '#007aff' : '#888'; 37 | // case 'heart': 38 | // return reactions[reaction] || hoveredReaction === reaction ? '#ff4d4f' : '#888'; 39 | // case 'laugh': 40 | // return reactions[reaction] || hoveredReaction === reaction ? '#ffd60a' : '#888'; 41 | // case 'angry': 42 | // return reactions[reaction] || hoveredReaction === reaction ? '#ff5349' : '#888'; 43 | // default: 44 | // return '#888'; 45 | // } 46 | // }; 47 | 48 | // return ( 49 | //
50 | // handleReactionClick('thumbs-up')} 54 | // onMouseEnter={() => handleMouseEnter('thumbs-up')} 55 | // onMouseLeave={handleMouseLeave} 56 | // beat={hoveredReaction === 'thumbs-up'} 57 | // color={getColor('thumbs-up')} 58 | // /> 59 | // {getReactionCount('thumbs-up')} 60 | // handleReactionClick('heart')} 64 | // onMouseEnter={() => handleMouseEnter('heart')} 65 | // onMouseLeave={handleMouseLeave} 66 | // beat={hoveredReaction === 'heart'} 67 | // color={getColor('heart')} 68 | // /> 69 | // {getReactionCount('heart')} 70 | // handleReactionClick('laugh')} 74 | // onMouseEnter={() => handleMouseEnter('laugh')} 75 | // onMouseLeave={handleMouseLeave} 76 | // beat={hoveredReaction === 'laugh'} 77 | // color={getColor('laugh')} 78 | // /> 79 | // {getReactionCount('laugh')} 80 | // handleReactionClick('angry')} 84 | // onMouseEnter={() => handleMouseEnter('angry')} 85 | // onMouseLeave={handleMouseLeave} 86 | // beat={hoveredReaction === 'angry'} 87 | // color={getColor('angry')} 88 | // /> 89 | // {getReactionCount('angry')} 90 | //
91 | // ); 92 | // }; 93 | 94 | // export default EmojiReaction; 95 | -------------------------------------------------------------------------------- /imports/closest/client.lua: -------------------------------------------------------------------------------- 1 | local GetActivePlayers , GetGamePool , GetEntityCoords , GetPlayerPed , IsPedAPlayer = GetActivePlayers, GetGamePool, GetEntityCoords, GetPlayerPed, IsPedAPlayer 2 | 3 | --- supv.closest.object - Get closest object 4 | ---@param coords vec3 5 | ---@param maxDistance? number-2 6 | ---@return number|nil, vec3|nil 7 | local function GetClosestObject(coords, maxDistance) 8 | local objects = GetGamePool('CObject') 9 | local closestObject, closestCoords 10 | maxDistance = maxDistance or 2 11 | 12 | for i = 1, #objects do 13 | local object = objects[i] 14 | 15 | local objectCoords = GetEntityCoords(object) 16 | local distance = #(coords - objectCoords) 17 | 18 | if distance < maxDistance then 19 | maxDistance = distance 20 | closestObject = object 21 | closestCoords = objectCoords 22 | end 23 | end 24 | 25 | return closestObject, closestCoords 26 | end 27 | 28 | --- supv.closest.player - Get closest player 29 | ---@param coords vec3 30 | ---@param maxDistance? number-2 31 | ---@param included? boolean 32 | ---@return number|nil, number|nil, vec3|nil 33 | local function GetClosestPlayer(coords, maxDistance, included) 34 | local players = GetActivePlayers() 35 | local targetId, targetPed, targetCoords 36 | maxDistance = maxDistance or 2 37 | 38 | for i = 1, #players do 39 | local playerid = players[i] 40 | 41 | if playerid ~= supv.cache.playerid or included then 42 | local playerPed = GetPlayerPed(playerid) 43 | local playerCoords = GetEntityCoords(playerPed) 44 | local distance = #(coords - playerCoords) 45 | 46 | if distance < maxDistance then 47 | maxDistance = distance 48 | targetId = playerid 49 | targetPed = playerPed 50 | targetCoords = playerCoords 51 | end 52 | end 53 | end 54 | 55 | return targetId, targetPed, targetCoords 56 | end 57 | 58 | --- supv.closest.vehicle - Get closest vehicle 59 | ---@param coords vec3 60 | ---@param maxDistance? number-2 61 | ---@param inside? boolean 62 | ---@return number|nil, vec3|nil 63 | local function GetClosestVehicle(coords, maxDistance, inside) 64 | local vehicles = GetGamePool('CVehicle') 65 | local closestVehicle, closestCoords 66 | maxDistance = maxDistance or 2 67 | 68 | for i = 1, #vehicles do 69 | local vehicle = vehicles[i] 70 | 71 | if not supv.cache.vehicle or supv.cache.vehicle ~= vehicle or inside then 72 | local vehicleCoords = GetEntityCoords(vehicle) 73 | local distance = #(coords - vehicleCoords) 74 | 75 | if distance < maxDistance then 76 | maxDistance = distance 77 | closestVehicle = vehicle 78 | closestCoords = vehicleCoords 79 | end 80 | end 81 | end 82 | 83 | return closestVehicle, closestCoords 84 | end 85 | 86 | --- supv.closest.ped - Get closest ped 87 | ---@param coords vec3 88 | ---@param maxDistance? number-2 89 | ---@return number|nil, vec3|nil 90 | local function GetClosestPed(coords, maxDistance) 91 | local peds = GetGamePool('CPed') 92 | local closestPed, closestCoords 93 | maxDistance = maxDistance or 2 94 | 95 | for i = 1, #peds do 96 | local ped = peds[i] 97 | 98 | if not IsPedAPlayer(ped) then 99 | local pedCoords = GetEntityCoords(ped) 100 | local distance = #(coords - pedCoords) 101 | 102 | if distance < maxDistance then 103 | maxDistance = distance 104 | closestPed = ped 105 | closestCoords = pedCoords 106 | end 107 | end 108 | end 109 | 110 | return closestPed, closestCoords 111 | end 112 | 113 | return { 114 | vehicle = GetClosestVehicle, 115 | object = GetClosestObject, 116 | player = GetClosestPlayer, 117 | ped = GetClosestPed, 118 | } -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local supv_core , service = 'supv_core', (IsDuplicityVersion() and 'server') or 'client' 4 | local LoadResourceFile , IsDuplicityVersion , joaat , await , GetCurrentResourceName = LoadResourceFile, IsDuplicityVersion, joaat, Citizen.Await, GetCurrentResourceName 5 | local GetGameName = GetGameName 6 | -- local eventCaches = {} 7 | 8 | ---@param str string 9 | ---@return string 10 | local function FormatByte(str) 11 | local binaryString = "" 12 | for i = 1, #str do 13 | local byte = str:byte(i) 14 | local bits = {} 15 | for j = 7, 0, -1 do 16 | bits[#bits + 1] = (byte >> j) & 1 17 | end 18 | binaryString = binaryString .. table.concat(bits) 19 | end 20 | return binaryString 21 | end 22 | 23 | ---@param name string 24 | ---@param from? string<'client' | 'server'> default is supv.service 25 | ---@return string 26 | local function FormatEvent(self, name, from) 27 | --if eventCaches[name] then return eventCaches[name] end 28 | --eventCaches[name] = FormatByte(("%s%s"):format(from and joaat(from) or joaat(service), joaat(name))) 29 | --return eventCaches[name] 30 | return FormatByte(("%s%s"):format(from and joaat(from) or joaat(service), joaat(name))) 31 | end 32 | 33 | local supv = setmetatable({ 34 | service = service, ---@type string<'client' | 'server'> 35 | name = supv_core, ---@type string<'supv_core'> 36 | env = supv_core, ---@type string<'resource_name?'> 37 | game = GetGameName(), ---@type string<'fivem' | 'redm'> 38 | hashEvent = FormatEvent, 39 | build = GetGameBuildNumber(), 40 | await = await, 41 | lang = GetConvar('supv:locale', 'fr') ---@type string<'fr' | 'en' | unknown> 42 | }, { 43 | __newindex = function(self, name, value) 44 | rawset(self, name, value) 45 | if type(value) == 'function' then 46 | exports(name, value) 47 | end 48 | end 49 | }) 50 | 51 | _ENV.supv = supv 52 | 53 | local loaded = {} 54 | package = { 55 | loaded = setmetatable({}, { 56 | __index = loaded, 57 | __newindex = function() end, 58 | __metatable = false, 59 | }), 60 | path = './?.lua;' 61 | } 62 | 63 | local _require = require 64 | function require(modname) 65 | 66 | local module = loaded[modname] 67 | if not module then 68 | if module == false then 69 | error(("^1circular-dependency occurred when loading module '%s'^0"):format(modname), 2) 70 | end 71 | 72 | local success, result = pcall(_require, modname) 73 | 74 | if success then 75 | loaded[modname] = result 76 | return result 77 | end 78 | 79 | local modpath = modname:gsub('%.', '/') 80 | local paths = { string.strsplit(';', package.path) } 81 | for i = 1, #paths do 82 | local scriptPath = paths[i]:gsub('%?', modpath):gsub('%.+%/+', '') 83 | local resourceFile = LoadResourceFile(supv_core, scriptPath) 84 | if resourceFile then 85 | loaded[modname] = false 86 | scriptPath = ('@@%s/%s'):format(supv_core, scriptPath) 87 | 88 | local chunk, err = load(resourceFile, scriptPath) 89 | 90 | if err or not chunk then 91 | loaded[modname] = nil 92 | return error(err or ("unable to load module '%s'"):format(modname), 3) 93 | end 94 | 95 | module = chunk(modname) or true 96 | loaded[modname] = module 97 | 98 | return module 99 | end 100 | end 101 | 102 | return error(("module '%s' not found"):format(modname), 2) 103 | end 104 | 105 | return module 106 | end 107 | 108 | local callback = require(('imports.callback.%s'):format(service)) 109 | 110 | if service == 'server' then 111 | require('imports.version.server').check('github', nil, 500) 112 | elseif service == 'client' then 113 | local cache = {} 114 | _ENV.cache = cache 115 | end 116 | 117 | _ENV.callback = callback -------------------------------------------------------------------------------- /imports/require/shared.lua: -------------------------------------------------------------------------------- 1 | -- credit: ox_lib 2 | if lib then return lib.require end 3 | local loaded = {} 4 | 5 | package = { 6 | loaded = setmetatable({}, { 7 | __index = loaded, 8 | __newindex = function() end, 9 | __metatable = false, 10 | }), 11 | path = './?.lua;' 12 | } 13 | 14 | local _require = require 15 | 16 | ---Loads the given module inside the current resource, returning any values returned by the file or `true` when `nil`. 17 | ---@param modname string 18 | ---@return unknown? 19 | local function LoadModule(modname) 20 | if type(modname) ~= 'string' then return end 21 | 22 | local module = loaded[modname] 23 | 24 | if not module then 25 | if module == false then 26 | error(("^1circular-dependency occurred when loading module '%s'^0"):format(modname), 2) 27 | end 28 | 29 | if not modname:find('^@') then 30 | local success, result = pcall(_require, modname) 31 | 32 | if success then 33 | loaded[modname] = result 34 | return result 35 | end 36 | 37 | local modpath = modname:gsub('%.', '/') 38 | 39 | for path in package.path:gmatch('[^;]+') do 40 | local scriptPath = path:gsub('?', modpath):gsub('%.+%/+', '') 41 | local resourceFile = LoadResourceFile(supv.env, scriptPath) 42 | 43 | if resourceFile then 44 | loaded[modname] = false 45 | scriptPath = ('@@%s/%s'):format(supv.env, scriptPath) 46 | 47 | local chunk, err = load(resourceFile, scriptPath) 48 | 49 | if err or not chunk then 50 | loaded[modname] = nil 51 | return error(err or ("unable to load module '%s'"):format(modname), 3) 52 | end 53 | 54 | module = chunk(modname) or true 55 | loaded[modname] = module 56 | 57 | return module 58 | end 59 | end 60 | else 61 | local rss, dir = modname:gsub('%.', '/'):match('^(.-)/(.+)$') 62 | 63 | if not rss or not dir then return error('Invalid path format: '..modname, 2) end 64 | rss, dir = rss:gsub('^@', ''), dir..'.lua' 65 | local chunk = LoadResourceFile(rss, dir) 66 | 67 | if chunk then 68 | local scriptPath = ('@@%s/%s'):format(rss, dir) 69 | local func, err = load(chunk, scriptPath) 70 | 71 | if err or not func then 72 | return error(err or ("unable to load module '%s'"):format(modname), 2) 73 | end 74 | 75 | module = func(modname) or true 76 | loaded[modname] = module 77 | 78 | return module 79 | end 80 | end 81 | 82 | return error(("module '%s' not found"):format(modname), 2) 83 | end 84 | 85 | return module 86 | end 87 | 88 | return LoadModule 89 | 90 | --[[ old method, not working with zones module because need require (glm) to calculate vector 91 | 92 | local moduleLoaded = {} 93 | 94 | local function load_module(path) 95 | if moduleLoaded[path] then 96 | return moduleLoaded[path] 97 | end 98 | 99 | local module_path = ("%s.lua"):format(path) 100 | local module_file = LoadResourceFile(GetCurrentResourceName(), module_path) 101 | if not module_file then 102 | error("Impossible de chargé le module : "..path) 103 | end 104 | 105 | moduleLoaded[path] = load(module_file)() 106 | return moduleLoaded[path] 107 | end 108 | 109 | local function call_module(path) 110 | path = path:gsub('%.', '/') 111 | local module = load_module(path) 112 | if not module then 113 | return error("Le module n'a pas charger : "..path) 114 | end 115 | return module 116 | end 117 | 118 | return { 119 | load = call_module 120 | } 121 | --]] -------------------------------------------------------------------------------- /web/src/features/modal/components/custom/_dateInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { DateInput } from "@mantine/dates"; 3 | import type { _DateInputProps } from "../../../../typings"; 4 | import dayjs from "dayjs"; 5 | import "dayjs/locale/fr"; 6 | //import { fr } from 'date-fns/locale'; 7 | 8 | interface DateType { 9 | status: "subtract" | "add"; 10 | type: "year" | "month" | "day"; 11 | value: number; 12 | } 13 | 14 | export const DateInputField: React.FC<_DateInputProps> = ({ 15 | index, 16 | label, 17 | data, 18 | onChanged, 19 | props, 20 | }) => { 21 | const [value, setValue] = useState(null); 22 | 23 | const formatDate = (date: Date | null): string => { 24 | return date 25 | ? date.toLocaleDateString("fr-FR", { 26 | day: "2-digit", 27 | month: "2-digit", 28 | year: "numeric", 29 | }) 30 | : ""; 31 | }; 32 | 33 | const handlerChange = (date: Date | null) => { 34 | setValue(date); 35 | onChanged(index, formatDate(date), data?.required, data?.callback); 36 | }; 37 | 38 | const onPlaceHolder = (date: Date | null): string => { 39 | return date 40 | ? formatDate(date) 41 | : new Date().toLocaleDateString("fr-FR", { 42 | day: "2-digit", 43 | month: "2-digit", 44 | year: "numeric", 45 | }); 46 | }; 47 | 48 | const selectOnDate = (data: DateType) => { 49 | return dayjs(new Date())[data.status](data?.value, data?.type).toDate(); 50 | }; 51 | 52 | return ( 53 | ({ 75 | day: { 76 | "&[data-selected]": { 77 | color: "white", 78 | background: "rgba(14, 44, 100, 0.86)", //theme.colors.blue[6], 79 | "&, &:hover": { 80 | color: "white", 81 | background: "rgba(14, 44, 100, 0.86)", 82 | }, 83 | }, 84 | "&:hover": { 85 | //color: theme.colors.blue[8], 86 | backgroundColor: "rgba(14, 44, 100, 0.86)", 87 | color: "white", 88 | }, 89 | //color: theme.colors.gray[1], 90 | }, 91 | month: { 92 | "&[data-selected]": { 93 | color: "white", 94 | background: "rgba(14, 44, 100, 0.86)", //theme.colors.blue[6], 95 | "&, &:hover": { 96 | color: "white", 97 | background: "rgba(14, 44, 100, 0.86)", 98 | }, 99 | }, 100 | "&:hover": { 101 | //color: theme.colors.blue[8], 102 | //backgroundColor: "rgba(14, 44, 100, 0.86)", 103 | //color: "white", 104 | }, 105 | //color: theme.colors.gray[1], 106 | }, 107 | yearsList: { 108 | "&[data-selected]": { 109 | color: "white", 110 | background: "rgba(14, 44, 100, 0.86)", //theme.colors.blue[6], 111 | "&, &:hover": { 112 | color: "white", 113 | background: "rgba(14, 44, 100, 0.86)", 114 | }, 115 | }, 116 | "&[data-hovered]": { 117 | //color: theme.colors.blue[8], 118 | background: "rgba(14, 44, 100, 0.86)", 119 | }, 120 | "&:hover": { 121 | //color: theme.colors.blue[8], 122 | //backgroundColor: "rgba(14, 44, 100, 0.86)", 123 | //color: "white", 124 | }, 125 | //color: theme.colors.gray[1], 126 | }, 127 | })} 128 | popoverProps={{ withinPortal: true, position: "top" }} 129 | minDate={data.minDate && selectOnDate(data.minDate)} 130 | maxDate={data.maxDate && selectOnDate(data.maxDate)} 131 | onChange={handlerChange} 132 | label={label} 133 | placeholder={onPlaceHolder(value)} 134 | valueFormat="DD/MM/YYYY" 135 | mx="xs" 136 | required={data?.required || false} 137 | error={props.error || false} 138 | withAsterisk={data?.required || false} 139 | /> 140 | ); 141 | }; 142 | -------------------------------------------------------------------------------- /web/src/typings/Modal.ts: -------------------------------------------------------------------------------- 1 | //import { ModalsProviderProps } from './config/modals'; 2 | import { MantineTransition } from '@mantine/core'; 3 | 4 | // Modal Props as cat all option 5 | export interface Option { 6 | type: string; 7 | label: string; 8 | name?: string; 9 | checked?: boolean; 10 | description?: string; 11 | required?: boolean; 12 | default?: string | boolean | Date | number | Array; 13 | format?: string; 14 | icon?: string | string[]; 15 | placeholder?: string; 16 | max?: number; 17 | min?: number; 18 | step?: number; 19 | data?: any; 20 | size?: string; 21 | error?: string; 22 | callback?: boolean; 23 | options?: ItemSelectProps[]; 24 | transition?: {name: MantineTransition, duration: number, timingFunction: string}; 25 | } 26 | 27 | export interface ModalPropsCustom { 28 | title: string; 29 | size?: string; 30 | withOverlay?: boolean; 31 | options: Option[]; 32 | useCallback?: boolean; 33 | canCancel?: boolean; 34 | transition?: {name: MantineTransition, duration: number, timingFunction: string}; 35 | } 36 | 37 | export interface ModalConfirmProps { 38 | title?: string; 39 | description?: string; 40 | size?: string; 41 | transition?: {name: MantineTransition, duration: number, timingFunction: string}; 42 | withOverlay?: boolean; 43 | } 44 | 45 | // _components 46 | 47 | export interface ItemSelectProps { 48 | Array: {label: string, value: string}[]; 49 | } 50 | 51 | export interface _SliderProps { 52 | index: string; 53 | label?: string; 54 | defaultValue?: number; 55 | min?: number; 56 | max?: number; 57 | step?: number; 58 | transition?: {name: MantineTransition, duration: number, timingFunction: string} 59 | onChanged: ( 60 | index: string, 61 | value: number, 62 | isRequired?: boolean, 63 | callback?: boolean 64 | ) => void; 65 | props: any; 66 | } 67 | 68 | export interface _SelectProps { 69 | index: string; 70 | label?: string; 71 | data: any; 72 | options: ItemSelectProps[], 73 | onChanged: ( 74 | index: string, 75 | value: string, 76 | isRequired?: boolean, 77 | callback?: boolean 78 | ) => void; 79 | props: any; 80 | } 81 | 82 | interface _DataColorPickers { 83 | format?: "hex" | "hexa" | "rgba" | "rgb" | "hsl" | "hsla"; 84 | default?: string; 85 | required?: boolean; 86 | callback?: boolean; 87 | } 88 | 89 | export interface _ColorPickerProps { 90 | index: string; 91 | label?: string; 92 | data?: _DataColorPickers; 93 | onChanged: ( 94 | index: string, 95 | value: string, 96 | isRequired?: boolean, 97 | callback?: boolean 98 | ) => void; 99 | props: any; 100 | } 101 | 102 | export interface _MultiSelectProps { 103 | index: string; 104 | label?: string; 105 | data: any; 106 | options: ItemSelectProps[], 107 | onChanged: ( 108 | index: string, 109 | value: string[], 110 | isRequired?: boolean, 111 | callback?: boolean 112 | ) => void; 113 | props: any; 114 | } 115 | 116 | export interface _PasswordProps { 117 | index: string; 118 | label?: string; 119 | data?: any; 120 | onChanged: ( 121 | index: string, 122 | value: string, 123 | isRequired?: boolean, 124 | callback?: boolean 125 | ) => void; 126 | props: any; 127 | } 128 | 129 | export interface _CheckboxProps { 130 | index: string; 131 | label?: string; 132 | data?: any; 133 | defaultValue?: boolean; 134 | onChanged: ( 135 | index: string, 136 | value: boolean, 137 | isRequired?: boolean, 138 | callback?: boolean 139 | ) => void; 140 | props: any; 141 | } 142 | 143 | export interface _DateInputProps { 144 | index: string; 145 | label?: string; 146 | data?: any; 147 | onChanged: ( 148 | index: string, 149 | value: string, 150 | isRequired?: boolean, 151 | callback?: boolean 152 | ) => void; 153 | props: any; 154 | } 155 | 156 | export interface _TextInputProps { 157 | index: string; 158 | label?: string; 159 | data?: any; 160 | onChanged: ( 161 | index: string, 162 | value: string, 163 | isRequired?: boolean, 164 | callback?: boolean 165 | ) => void; 166 | props: any; 167 | } 168 | 169 | export interface _NumberInputProps { 170 | index: string; 171 | label?: string; 172 | data?: any; 173 | onChanged: ( 174 | index: string, 175 | value: number, 176 | isRequired?: boolean, 177 | callback?: boolean, 178 | ) => void; 179 | props: any; 180 | } -------------------------------------------------------------------------------- /imports/vehicle/server.lua: -------------------------------------------------------------------------------- 1 | local CreateVehicleServerSetter = CreateVehicleServerSetter 2 | local GetGameTimer = GetGameTimer 3 | local GetPedInVehicleSeat = GetPedInVehicleSeat 4 | local SetPedIntoVehicle = SetPedIntoVehicle 5 | local GetEntityCoords = GetEntityCoords 6 | local GetEntityHeading = GetEntityHeading 7 | local DeleteEntity = DeleteEntity 8 | local GetPlayerPed = GetPlayerPed 9 | local GetAllVehicles = GetAllVehicles 10 | local GetVehicleNumberPlateText = GetVehicleNumberPlateText 11 | 12 | local Vehicles = {} 13 | 14 | local function GetAllVehiclesCreated() 15 | return Vehicles 16 | end 17 | 18 | local function RemoveVehicle(self) 19 | if DoesEntityExist(self.vehicle) then 20 | DeleteEntity(self.vehicle) 21 | Vehicles[self.id] = nil 22 | end 23 | return nil, collectgarbage() 24 | end 25 | 26 | local function SpawnVehicle(model, _type, coords, data, properties) 27 | local p = promise.new() 28 | 29 | CreateThread(function() 30 | local self = { 31 | model = model, 32 | properties = properties, 33 | vehicle = nil, 34 | type = _type, 35 | vec3 = vec3(coords.x, coords.y, coords.z), 36 | vec4 = vec4(coords.x, coords.y, coords.z, coords.w or 0.0), 37 | } 38 | 39 | if self.model and self.vec3 then 40 | self.vehicle = CreateVehicleServerSetter(self.model, self.type, self.vec4.x, self.vec4.y, self.vec4.z, self.vec4.w) 41 | if DoesEntityExist(self.vehicle) then 42 | self.remove = RemoveVehicle 43 | if data.plate then 44 | self.plate = data.plate 45 | SetVehicleNumberPlateText(self.vehicle, self.plate) 46 | else 47 | self.plate = GetVehicleNumberPlateText(self.vehicle) 48 | end 49 | 50 | local ped 51 | Wait(500) 52 | for i = -1, 6 do 53 | ped = GetPedInVehicleSeat(self.vehicle, i) 54 | local popType = ped and GetEntityPopulationType(ped) 55 | if popType and popType <= 5 or popType >= 1 then 56 | DeleteEntity(ped) 57 | end 58 | end 59 | 60 | --local entityOwner = NetworkGetEntityOwner(self.vehicle) 61 | --local playerId = NetworkGetNetworkIdFromEntity(entityOwner) 62 | self.netId = NetworkGetNetworkIdFromEntity(self.vehicle) 63 | 64 | --if playerId then 65 | -- print('playerID as owner: ', playerId) 66 | -- if self.properties and next(self.properties) then 67 | -- supv.setVehiclesProperties(playerId, self.netId, self.properties, --data.fixed or false) 68 | -- elseif not self.properties then 69 | -- self.properties = supv.getVehiclesProperties(playerId, self.netId) 70 | -- end 71 | --end 72 | 73 | self.plate = (self.plate):gsub('%s+', ''):upper() 74 | local id = #Vehicles + 1 75 | self.id = id 76 | Vehicles[self.id] = self 77 | p:resolve(self) 78 | else 79 | p:resolve(false) 80 | end 81 | else 82 | p:resolve(false) 83 | end 84 | end) 85 | 86 | return supv.await(p) 87 | end 88 | 89 | local function SearchVehicle(plate) 90 | local p = promise.new() 91 | 92 | CreateThread(function() 93 | local vehicles = GetAllVehicles() 94 | if vehicles and #vehicles > 0 then 95 | plate = plate:gsub('%s+', ''):upper() 96 | for i = 1, #vehicles do 97 | local vehicle = vehicles[i] 98 | if vehicle and DoesEntityExist(vehicle) then 99 | local plateVehicle = GetVehicleNumberPlateText(vehicle):gsub('%s+', ''):upper() 100 | if plateVehicle == plate then 101 | p:resolve(vehicle) 102 | return 103 | end 104 | end 105 | end 106 | end 107 | p:resolve(false) 108 | end) 109 | 110 | return supv.await(p) 111 | end 112 | 113 | return { 114 | spawn = SpawnVehicle, 115 | search = SearchVehicle, 116 | getAllCreated = GetAllVehiclesCreated, 117 | } -------------------------------------------------------------------------------- /imports/points/client.lua: -------------------------------------------------------------------------------- 1 | --- credit: ox_lib 2 | if lib then return lib.points end 3 | 4 | ---@class PointProperties 5 | ---@field coords vector3 6 | ---@field distance number 7 | ---@field onEnter? fun(self: CPoint) 8 | ---@field onExit? fun(self: CPoint) 9 | ---@field nearby? fun(self: CPoint) 10 | ---@field [string] any 11 | 12 | ---@class CPoint : PointProperties 13 | ---@field id number 14 | ---@field currentDistance number 15 | ---@field isClosest? boolean 16 | ---@field remove fun() 17 | 18 | ---@type table 19 | local points = {} 20 | ---@type CPoint[] 21 | local nearbyPoints = {} 22 | local nearbyCount = 0 23 | ---@type CPoint? 24 | local closestPoint 25 | local tick 26 | 27 | local function removePoint(self) 28 | if closestPoint?.id == self.id then 29 | closestPoint = nil 30 | end 31 | 32 | points[self.id] = nil 33 | end 34 | 35 | CreateThread(function() 36 | local player = supv.player.get() 37 | while true do 38 | if nearbyCount ~= 0 then 39 | table.wipe(nearbyPoints) 40 | nearbyCount = 0 41 | end 42 | 43 | if closestPoint and player:distance(closestPoint.coords) > closestPoint.distance then 44 | closestPoint = nil 45 | end 46 | 47 | for _, point in pairs(points) do 48 | player:distance(point.coords) 49 | 50 | if player.dist <= point.distance then 51 | point.currentDistance = player.dist 52 | 53 | if closestPoint then 54 | if player.dist < closestPoint.currentDistance then 55 | closestPoint.isClosest = nil 56 | point.isClosest = true 57 | closestPoint = point 58 | end 59 | elseif player.dist < point.distance then 60 | point.isClosest = true 61 | closestPoint = point 62 | end 63 | 64 | if point.nearby then 65 | nearbyCount += 1 66 | nearbyPoints[nearbyCount] = point 67 | end 68 | 69 | if point.onEnter and not point.inside then 70 | point.inside = true 71 | point:onEnter() 72 | end 73 | elseif point.currentDistance then 74 | if point.onExit then point:onExit() end 75 | point.inside = nil 76 | point.currentDistance = nil 77 | end 78 | end 79 | 80 | if not tick then 81 | if nearbyCount ~= 0 then 82 | tick = SetInterval(function() 83 | for i = 1, nearbyCount do 84 | local point = nearbyPoints[i] 85 | 86 | if point then 87 | point:nearby() 88 | end 89 | end 90 | end) 91 | end 92 | elseif nearbyCount == 0 then 93 | tick = ClearInterval(tick) 94 | end 95 | 96 | Wait(300) 97 | end 98 | end) 99 | 100 | local function toVector(coords) 101 | local _type = type(coords) 102 | 103 | if _type ~= 'vector3' then 104 | if _type == 'table' or _type == 'vector4' then 105 | return vec3(coords[1] or coords.x, coords[2] or coords.y, coords[3] or coords.z) 106 | end 107 | 108 | error(("expected type 'vector3' or 'table' (received %s)"):format(_type)) 109 | end 110 | 111 | return coords 112 | end 113 | 114 | local function New(...) 115 | local args = {...} 116 | local id = #points + 1 117 | local self 118 | 119 | -- Support sending a single argument containing point data 120 | if type(args[1]) == 'table' then 121 | self = args[1] 122 | self.id = id 123 | self.remove = removePoint 124 | else 125 | -- Backwards compatibility for original implementation (args: coords, distance, data) 126 | self = { 127 | id = id, 128 | coords = args[1], 129 | remove = removePoint, 130 | } 131 | end 132 | 133 | self.coords = toVector(self.coords) 134 | self.distance = self.distance or args[2] 135 | 136 | if args[3] then 137 | for k, v in pairs(args[3]) do 138 | self[k] = v 139 | end 140 | end 141 | 142 | points[id] = self 143 | 144 | return self 145 | end 146 | 147 | local function getAllPoints() 148 | return points 149 | end 150 | 151 | local function getNearbyPoints() 152 | return nearbyPoints 153 | end 154 | 155 | local function getClosestPoint() 156 | return closestPoint 157 | end 158 | 159 | return { 160 | new = New, 161 | getAllPoints = getAllPoints, 162 | getNearbyPoints = getNearbyPoints 163 | } --------------------------------------------------------------------------------