├── .github └── dependabot.yml ├── .gitignore ├── LICENSE ├── README.md ├── config ├── client │ └── marker.lua ├── modules.lua ├── server │ └── webhook.lua └── shared │ └── handlers.lua ├── data ├── client │ └── marker.json ├── server │ └── webhook.json └── shared │ └── handlers.json ├── fxmanifest.lua ├── imports ├── area │ └── client.lua ├── blips │ └── client.lua ├── callback │ ├── client.lua │ └── server.lua ├── class │ └── shared.lua ├── closest │ └── client.lua ├── disableControls │ └── client.lua ├── emit │ ├── client.lua │ └── server.lua ├── exports │ └── shared.lua ├── exportsClass │ └── shared.lua ├── framework │ ├── client.lua │ ├── esx │ │ ├── client.lua │ │ └── server.lua │ ├── qb │ │ ├── client.lua │ │ └── server.lua │ └── server.lua ├── getCrosshairSave │ └── client.lua ├── inventory │ ├── esx │ │ └── server.lua │ ├── ox │ │ └── server.lua │ ├── qb │ │ └── server.lua │ └── server.lua ├── json │ ├── client.lua │ └── server.lua ├── locale │ └── shared.lua ├── marker │ └── client.lua ├── math │ └── shared.lua ├── mysql │ └── server.lua ├── npc │ └── client.lua ├── on │ ├── client.lua │ └── server.lua ├── os │ └── server.lua ├── playAnim │ └── client.lua ├── player │ └── client.lua ├── points │ └── client.lua ├── promise │ └── shared.lua ├── prompt │ └── client.lua ├── raycast │ └── client.lua ├── request │ └── client.lua ├── require │ └── shared.lua ├── string │ └── shared.lua ├── table │ └── shared.lua ├── time │ ├── client.lua │ └── server.lua ├── timeout │ └── shared.lua ├── tool │ └── client.lua ├── utf8 │ └── shared.lua ├── vehicle │ ├── client.lua │ └── server.lua ├── version │ └── server.lua ├── waitFor │ └── shared.lua └── zones │ └── client.lua ├── init.lua ├── locales ├── en.json └── fr.json ├── modules ├── handlers │ ├── client │ │ ├── ace.lua │ │ ├── blips.lua │ │ ├── events.lua │ │ ├── nui.lua │ │ └── vehicles.lua │ ├── index.lua │ └── server │ │ ├── ace.lua │ │ ├── events.lua │ │ ├── vehicles.lua │ │ └── webhook.lua ├── init.lua ├── main │ ├── client │ │ ├── cache.lua │ │ └── resource.lua │ ├── index.lua │ └── server │ │ └── resource.lua └── nui │ ├── client │ ├── action.lua │ ├── billing.lua │ ├── config.lua │ ├── crosshair.lua │ ├── modals.lua │ └── notify.lua │ ├── index.lua │ └── server │ └── notify.lua ├── obj.lua ├── version.json └── web ├── .gitignore ├── README.md ├── craco.config.js ├── package-lock.json ├── package.json ├── public └── index.html ├── src ├── App.tsx ├── animation │ ├── icones.ts │ ├── keyframes │ │ └── notifcation.ts │ └── notifications.ts ├── dev │ ├── DevEnv.tsx │ ├── config │ │ ├── emojipicker.ts │ │ ├── index.ts │ │ └── notifications.ts │ └── debug │ │ ├── action.ts │ │ ├── billing.ts │ │ ├── copy.ts │ │ ├── crosshairTool.ts │ │ ├── dialog.ts │ │ ├── modals │ │ ├── confirm.ts │ │ └── custom.ts │ │ ├── notifcation.ts │ │ └── resource.ts ├── features │ ├── action │ │ └── ActionWrapper.tsx │ ├── billing │ │ ├── Billing.tsx │ │ └── _components │ │ │ ├── amende.tsx │ │ │ ├── index.ts │ │ │ ├── item_service.tsx │ │ │ └── normal.tsx │ ├── chat │ │ ├── Chat.tsx │ │ ├── Emoji.tsx │ │ └── Reactions.tsx │ ├── crosshair │ │ └── Crosshair.tsx │ ├── dialog │ │ └── Dialog.tsx │ ├── modal │ │ ├── ModalConfirm.tsx │ │ ├── ModalCustom.tsx │ │ └── components │ │ │ ├── buttons.tsx │ │ │ ├── custom │ │ │ ├── _checkbox.tsx │ │ │ ├── _colorpicker.tsx │ │ │ ├── _dateInput.tsx │ │ │ ├── _input.tsx │ │ │ ├── _multiselect.tsx │ │ │ ├── _number.tsx │ │ │ ├── _password.tsx │ │ │ ├── _select.tsx │ │ │ ├── _slider.tsx │ │ │ └── index.ts │ │ │ └── dateInput.tsx │ ├── notify │ │ ├── Notification.tsx │ │ ├── SimpleNotifyWrapp.tsx │ │ └── components │ │ │ ├── _action.tsx │ │ │ ├── _description.tsx │ │ │ ├── _title.tsx │ │ │ └── index.ts │ ├── resource │ │ ├── components │ │ │ ├── _array_switch.tsx │ │ │ ├── _badge.tsx │ │ │ ├── _buttons.tsx │ │ │ ├── _canAdd.tsx │ │ │ ├── _input.tsx │ │ │ ├── _object_custom.tsx │ │ │ ├── _object_string.tsx │ │ │ ├── _object_switch.tsx │ │ │ ├── _switch.tsx │ │ │ └── index.ts │ │ └── main.tsx │ └── tool │ │ ├── ConvertUnix.tsx │ │ └── Crosshair.tsx ├── hooks │ └── useNuiEvent.ts ├── index.css ├── index.tsx ├── providers │ └── ConfigProvider.tsx ├── react-app-env.d.ts ├── setupTests.ts ├── theme │ └── index.ts ├── typings │ ├── ConvertUnix.ts │ ├── Crosshair.tsx │ ├── Dialog.ts │ ├── InputDate.ts │ ├── Modal.ts │ ├── Notification.ts │ ├── Resource.ts │ ├── config │ │ ├── emojipicker.ts │ │ ├── modals.ts │ │ └── notifications.ts │ └── index.ts └── utils │ ├── debugData.ts │ ├── fetchNui.ts │ ├── index.ts │ ├── markdown.tsx │ └── misc.ts ├── tsconfig.json └── yarn.lock /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | web/yarn.lock 3 | -------------------------------------------------------------------------------- /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: -------------------------------------------------------------------------------- /config/client/marker.lua: -------------------------------------------------------------------------------- 1 | local loadjson = require 'imports.json.client'.load 2 | local marker = loadjson('data/client/marker') 3 | 4 | marker[1], marker[2] = marker["1"], marker["2"] 5 | marker["1"], marker["2"] = nil, nil 6 | 7 | return marker 8 | 9 | --[[ 10 | return { -- https://docs.fivem.net/natives/?_0x28477EC23D892089 11 | double = false, -- double default 12 | 13 | -- marker 1 default value 14 | [1] = { 15 | id = 27, 16 | z = -0.99, 17 | color1 = {6, 34, 86, 100}, -- default or inside color 18 | color2 = {255, 255, 255, 100}, -- optional and outside color 19 | dir = vec3(0.0, 0.0, 0.0), --{0.0,0.0,0.0}, 20 | rot = vec3(0.0, 0.0, 0.0), 21 | scale = vec3(2.0, 2.0, 2.0), 22 | updown = false, 23 | faceToCam = false, 24 | p19 = 2, 25 | rotate = false, 26 | textureDict = nil, 27 | textureName = nil, 28 | drawOnEnts = false 29 | }, 30 | 31 | -- marker 2 (optional) default value 32 | [2] = { 33 | id = 20, 34 | z = 0.5, 35 | color1 = {6, 34, 86, 100}, -- default or inside color 36 | color2 = {255, 255, 255, 100}, -- optional and outside color 37 | dir = vec3(0.0, 0.0, 0.0), 38 | rot = vec3(0.0, 180.0, 0.0), 39 | scale = vec3(0.5, 0.5, 0.2), 40 | updown = false, 41 | faceToCam = false, 42 | p19 = 2, 43 | rotate = true, 44 | textureDict = nil, 45 | textureName = nil, 46 | drawOnEnts = false 47 | } 48 | } 49 | ]] -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | --]] -------------------------------------------------------------------------------- /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') -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /data/shared/handlers.json: -------------------------------------------------------------------------------- 1 | { 2 | "blips": true 3 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /imports/callback/client.lua: -------------------------------------------------------------------------------- 1 | -- credit: ox_lib 2 | local events, timers, nameEvent = {}, {}, ('__supv_cb:%s') 3 | local RegisterNetEvent , TriggerServerEvent , pcall , unpack = RegisterNetEvent, TriggerServerEvent, pcall, table.unpack 4 | 5 | RegisterNetEvent(nameEvent:format(supv.name), function(name, ...) 6 | local cb = events[name] 7 | return cb and cb(...) 8 | end) 9 | 10 | local function EventTimer(name, timer) 11 | if type(timer) == 'number' and timer > 0 then 12 | local time = GetGameTimer() 13 | 14 | if (timers[name] or 0) > time then 15 | return false 16 | end 17 | 18 | timers[name] = time + timer 19 | end 20 | 21 | return true 22 | end 23 | 24 | local function TriggerServerCallback(name, timer, cb, ...) 25 | if not EventTimer(name, timer) then return end 26 | 27 | local k 28 | 29 | repeat 30 | k = ('%s:%s'):format(name, math.random(0, 999999)) 31 | until not events[name] 32 | 33 | TriggerServerEvent(nameEvent:format(supv:hashEvent(name, 'cb')), supv.name, k, ...) 34 | 35 | local p = not cb and promise.new() or nil 36 | 37 | events[k] = function (resp, ...) 38 | resp = {resp, ...} 39 | events[k] = nil 40 | 41 | if p then 42 | return p:resolve(resp) 43 | elseif cb then 44 | cb(unpack(resp)) 45 | end 46 | end 47 | 48 | if p then 49 | return unpack(supv.await(p)) 50 | end 51 | end 52 | 53 | local function CallbackSync(name, timer, ...) 54 | return TriggerServerCallback(name, timer, nil, ...) 55 | end 56 | 57 | local function CallackResponse(success, result, ...) 58 | if not success then 59 | if result then 60 | return error(("ERROR callback : %s"):format(result)) 61 | end 62 | return false 63 | end 64 | return result, ... 65 | end 66 | 67 | local function RegisterCallback(name, cb) 68 | RegisterNetEvent(nameEvent:format(supv:hashEvent(name, 'cb')), function(resource, k, ...) 69 | TriggerServerEvent(nameEvent:format(resource), k, CallackResponse(pcall(cb, ...))) 70 | end) 71 | end 72 | 73 | return { 74 | register = RegisterCallback, 75 | sync = CallbackSync, 76 | async = TriggerServerCallback 77 | } -------------------------------------------------------------------------------- /imports/callback/server.lua: -------------------------------------------------------------------------------- 1 | -- credit: ox_lib 2 | local events, nameEvent = {}, ('__supv_cb:%s') 3 | local RegisterNetEvent , TriggerClientEvent , pcall , unpack = RegisterNetEvent, TriggerClientEvent, pcall, table.unpack 4 | 5 | RegisterNetEvent(nameEvent:format(supv.name), function(name, ...) 6 | local cb = events[name] 7 | return cb and cb(...) 8 | end) 9 | 10 | local function TriggerClientCallback(name, source, cb, ...) 11 | local k 12 | 13 | repeat 14 | k = ('%s:%s:%s'):format(name, math.random(0, 999999), source) 15 | until not events[k] 16 | 17 | TriggerClientEvent(nameEvent:format(supv:hashEvent(name, 'cb')), source, supv.name, k, ...) 18 | 19 | local p = not cb and promise.new() or nil 20 | 21 | events[k] = function(resp, ...) 22 | resp = {resp, ...} 23 | events[k] = nil 24 | 25 | if p then 26 | return p:resolve(resp) 27 | elseif cb then 28 | cb(unpack(resp)) 29 | end 30 | end 31 | 32 | if p then 33 | return unpack(supv.await(p)) 34 | end 35 | end 36 | 37 | local function CallbackSynchrone(name, source, ...) 38 | return TriggerClientCallback(name, source, nil, ...) 39 | end 40 | 41 | local function CallackResponse(success, result, ...) 42 | if not success then 43 | if result then 44 | return error(("ERROR callback : %s"):format(result)) 45 | end 46 | return false 47 | end 48 | return result, ... 49 | end 50 | 51 | local function RegisterCallback(name, cb, ...) 52 | RegisterNetEvent(nameEvent:format(supv:hashEvent(name, 'cb')), function(resource, k, ...) 53 | TriggerClientEvent(nameEvent:format(resource), source, k, CallackResponse(pcall(cb, source, ...))) 54 | end) 55 | end 56 | 57 | return { 58 | register = RegisterCallback, 59 | sync = CallbackSynchrone, 60 | async = TriggerClientCallback 61 | } -------------------------------------------------------------------------------- /imports/class/shared.lua: -------------------------------------------------------------------------------- 1 | local mt_pvt = { 2 | __metatable = 'private', 3 | __ext = 0, 4 | __pack = function() return '' end, 5 | } 6 | 7 | ---@param obj table 8 | ---@return table 9 | local function NewInstance(self, obj) 10 | if obj.private then 11 | setmetatable(obj.private, mt_pvt) 12 | end 13 | 14 | setmetatable(obj, self) 15 | 16 | if self.Init then obj:Init() end 17 | 18 | if obj.export then 19 | self.__export[obj.export] = obj 20 | end 21 | 22 | return obj 23 | end 24 | 25 | ---@param name string 26 | ---@param super? table 27 | ---@param exportMethod? boolean 28 | ---@return table 29 | function supv.class(name, super, exportMethod) 30 | local self = { 31 | __name = name, 32 | New = NewInstance 33 | } 34 | 35 | self.__index = self 36 | 37 | if exportMethod and not super then 38 | self.__exportMethod = {} 39 | self.__export = {} 40 | 41 | setmetatable(self, { 42 | __newindex = function(_, key, value) 43 | rawset(_, key, value) 44 | self.__exportMethod[key] = true 45 | end 46 | }) 47 | 48 | exports('GetExportMethod', function() 49 | return self.__exportMethod 50 | end) 51 | 52 | exports('CallExportMethod', function(name, method, ...) 53 | local export = self.__export[name] 54 | return export[method](export, ...) 55 | end) 56 | end 57 | 58 | return super and setmetatable(self, { 59 | __index = super, 60 | __newindex = function(_, key, value) 61 | rawset(_, key, value) 62 | if type(value) == 'function' then 63 | self.__exportMethod[key] = true 64 | end 65 | end 66 | }) or self 67 | end 68 | 69 | return supv.class -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /imports/disableControls/client.lua: -------------------------------------------------------------------------------- 1 | local stock = {} 2 | local DisableControlAction = DisableControlAction 3 | 4 | 5 | ---@todo need finish this module 6 | 7 | --- oop 8 | 9 | local function Add(self, keys) 10 | if self.type == 'table' then 11 | for i = 1, #self.keys do 12 | local k = self.keys[i] 13 | 14 | end 15 | else return end 16 | end 17 | 18 | local function Remove(self, keys) 19 | if self.type == 'table' then 20 | 21 | else 22 | stock[self.id] = self:clear() 23 | end 24 | end 25 | 26 | local function Clear(self) 27 | if stock[self.id] then 28 | stock[self.id] = nil 29 | return nil, collectgarbage() 30 | end 31 | end 32 | 33 | local function Play(self) 34 | if self.type == 'table' then 35 | for i = 1, #self.keys do 36 | local v = self.keys[i] 37 | DisableControlAction(0, v, true) 38 | end 39 | else 40 | DisableControlAction(0, self.keys, true) 41 | end 42 | end 43 | 44 | local function Register(keys) 45 | local self = {} 46 | 47 | self.id = #stock + 1 48 | self.type = type(keys) 49 | 50 | if self.type == 'table' then 51 | if table.type(keys) ~= 'array' then return end 52 | self.keys = keys 53 | -- todo 54 | elseif self.type == 'number' then 55 | if math.type(keys) ~= 'integer' then return end 56 | self.keys = keys 57 | -- todo 58 | end 59 | 60 | self.remove = Remove 61 | self.add = Add 62 | self.clear = Clear 63 | self.play = Play 64 | 65 | stock[self.id] = self 66 | return self 67 | end 68 | 69 | --- not oop 70 | 71 | -- todo 72 | local function Disable(keys) 73 | if type(keys) == 'table' and table.type(keys) == 'array' then 74 | for i = 1, keys do 75 | local k = keys[i] 76 | DisableControlAction(0, k, true) 77 | end 78 | elseif type(keys) == 'number' and math.type(keys) == 'integer' then 79 | DisableControlAction(0, keys, true) 80 | else return end 81 | end 82 | 83 | return { 84 | -- oop 85 | new = Register, 86 | -- default 87 | disable = Disable 88 | } -------------------------------------------------------------------------------- /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/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 -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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/esx/client.lua: -------------------------------------------------------------------------------- 1 | local ESX = exports["es_extended"]:getSharedObject() 2 | local PlayerData = setmetatable({}, { 3 | __index = function(self, key) 4 | local value = rawget(self, key) 5 | if not value then 6 | if key ~= 'gang' then 7 | value = ESX.PlayerData?[key] 8 | rawset(self, key, value) 9 | else 10 | value = ESX.PlayerData?.faction 11 | rawset(self, key, value) 12 | end 13 | end 14 | return value 15 | end 16 | }) 17 | 18 | RegisterNetEvent("esx:playerLoaded", function(xPlayer) 19 | PlayerData.job = xPlayer.PlayerData?.job 20 | PlayerData.gang = xPlayer.PlayerData?.faction 21 | end) 22 | 23 | AddEventHandler('esx:setPlayerData', function(key, value) 24 | if GetInvokingResource() ~= 'es_extended' then return end 25 | Wait(500) 26 | if key == 'job' then 27 | PlayerData.job = value 28 | elseif key == 'faction' then -- or 'job2' 29 | PlayerData.gang = value 30 | end 31 | end) 32 | 33 | local function OnPlayerLoaded(cb) 34 | RegisterNetEvent('esx:playerLoaded', function(player, isNew, skin) 35 | cb(player, isNew, skin) 36 | end) 37 | end 38 | 39 | local function OnSetPlayerData(cb) 40 | RegisterNetEvent('esx:setPlayerData', function(key, value) 41 | cb(key, value) 42 | end) 43 | end 44 | 45 | return { 46 | playerData = PlayerData, 47 | onSetPlayerData = OnSetPlayerData, 48 | onPlayerLoaded = OnPlayerLoaded, 49 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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 | ]] -------------------------------------------------------------------------------- /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/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 -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /imports/inventory/ox/server.lua: -------------------------------------------------------------------------------- 1 | local export = 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 -------------------------------------------------------------------------------- /imports/inventory/qb/server.lua: -------------------------------------------------------------------------------- 1 | ---@todo -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /imports/json/client.lua: -------------------------------------------------------------------------------- 1 | local LoadResourceFile = 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 = resourceName or supv.env 9 | local path = filePath:gsub('%.json$', ''):gsub('%.', '/') 10 | local str = 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 | } -------------------------------------------------------------------------------- /imports/json/server.lua: -------------------------------------------------------------------------------- 1 | local LoadResourceFile = LoadResourceFile 2 | local SaveResourceFile = SaveResourceFile 3 | 4 | --- supv.json.load 5 | ---@param filePath string 6 | ---@param resourceName? string 7 | ---@return string 8 | local function Loadjson(filePath, resourceName) 9 | local resource = resourceName or supv.env 10 | local path = filePath:gsub('%.json$', ''):gsub('%.', '/') 11 | local str = json.decode(LoadResourceFile(resource, ("%s.json"):format(path))) 12 | if not str then 13 | error(('[ERROR] : Le fichier (%s) dans la ressource => %s, n\'a pas pu être chargé'):format(path, resource), 2) 14 | end 15 | return str 16 | end 17 | 18 | --- supv.json.save 19 | ---@param filePath string 20 | ---@param data table 21 | ---@param resourceName? string | nil 22 | ---@param dataLength? integer 23 | ---@return boolean 24 | local function Writejson(filePath, data, resourceName, dataLength) 25 | local resource = resourceName or supv.env 26 | local lenght = dataLength or -1 27 | local path = filePath:gsub('%.json$', ''):gsub('%.', '/') 28 | local writeFile = SaveResourceFile(resource, ("%s.json"):format(path), json.encode(data, {indent = true}), lenght) 29 | if not writeFile then 30 | error(('[ERROR] : Le fichier (%s) dans la ressource => %s, n\'a pas pu être sauvegardé'):format(path, resource), 2) 31 | return false 32 | end 33 | return true 34 | end 35 | 36 | return { 37 | load = Loadjson, 38 | save = Writejson 39 | } -------------------------------------------------------------------------------- /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/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 -------------------------------------------------------------------------------- /imports/on/client.lua: -------------------------------------------------------------------------------- 1 | local RegisterNetEvent , AddEventHandler = 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 -------------------------------------------------------------------------------- /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/playAnim/client.lua: -------------------------------------------------------------------------------- 1 | ---@credit overextended 2 | 3 | ---@alias AnimationFlags number 4 | ---| 0 DEFAULT 5 | ---| 1 LOOPING 6 | ---| 2 HOLD_LAST_FRAME 7 | ---| 4 REPOSITION_WHEN_FINISHED 8 | ---| 8 NOT_INTERRUPTABLE 9 | ---| 16 UPPERBODY 10 | ---| 32 SECONDARY 11 | ---| 64 REORIENT_WHEN_FINISHED 12 | ---| 128 ABORT_ON_PED_MOVEMENT 13 | ---| 256 ADDITIVE 14 | ---| 512 TURN_OFF_COLLISION 15 | ---| 1024 OVERRIDE_PHYSICS 16 | ---| 2048 IGNORE_GRAVITY 17 | ---| 4096 EXTRACT_INITIAL_OFFSET 18 | ---| 8192 EXIT_AFTER_INTERRUPTED 19 | ---| 16384 TAG_SYNC_IN 20 | ---| 32768 TAG_SYNC_OUT 21 | ---| 65536 TAG_SYNC_CONTINUOUS 22 | ---| 131072 FORCE_START 23 | ---| 262144 USE_KINEMATIC_PHYSICS 24 | ---| 524288 USE_MOVER_EXTRACTION 25 | ---| 1048576 HIDE_WEAPON 26 | ---| 2097152 ENDS_IN_DEAD_POSE 27 | ---| 4194304 ACTIVATE_RAGDOLL_ON_COLLISION 28 | ---| 8388608 DONT_EXIT_ON_DEATH 29 | ---| 16777216 ABORT_ON_WEAPON_DAMAGE 30 | ---| 33554432 DISABLE_FORCED_PHYSICS_UPDATE 31 | ---| 67108864 PROCESS_ATTACHMENTS_ON_START 32 | ---| 134217728 EXPAND_PED_CAPSULE_FROM_SKELETON 33 | ---| 268435456 USE_ALTERNATIVE_FP_ANIM 34 | ---| 536870912 BLENDOUT_WRT_LAST_FRAME 35 | ---| 1073741824 USE_FULL_BLENDING 36 | 37 | ---@alias ControlFlags number 38 | ---| 0 NONE 39 | ---| 1 DISABLE_LEG_IK 40 | ---| 2 DISABLE_ARM_IK 41 | ---| 4 DISABLE_HEAD_IK 42 | ---| 8 DISABLE_TORSO_IK 43 | ---| 16 DISABLE_TORSO_REACT_IK 44 | ---| 32 USE_LEG_ALLOW_TAGS 45 | ---| 64 USE_LEG_BLOCK_TAGS 46 | ---| 128 USE_ARM_ALLOW_TAGS 47 | ---| 256 USE_ARM_BLOCK_TAGS 48 | ---| 512 PROCESS_WEAPON_HAND_GRIP 49 | ---| 1024 USE_FP_ARM_LEFT 50 | ---| 2048 USE_FP_ARM_RIGHT 51 | ---| 4096 DISABLE_TORSO_VEHICLE_IK 52 | ---| 8192 LINKED_FACIAL 53 | 54 | ---@param ped number 55 | ---@param animDictionary string 56 | ---@param animationName string 57 | ---@param blendInSpeed? number Defaults to 8.0 58 | ---@param blendOutSpeed? number Defaults to -8.0 59 | ---@param duration? integer Defaults to -1 60 | ---@param animFlags? AnimationFlags 61 | ---@param startPhase? number 62 | ---@param phaseControlled? boolean 63 | ---@param controlFlags? integer 64 | ---@param overrideCloneUpdate? boolean 65 | function supv.playAnim(ped, animDictionary, animationName, blendInSpeed, blendOutSpeed, duration, animFlags, startPhase, phaseControlled, controlFlags, overrideCloneUpdate) 66 | local request = supv.request 67 | request:animDict(animDictionary) 68 | --supv.request.animDict(animDictionary) 69 | TaskPlayAnim(ped, animDictionary, animationName, blendInSpeed or 8.0, blendOutSpeed or -8.0, duration or -1, animFlags or 0, startPhase or 0.0, phaseControlled or false, controlFlags or 0, overrideCloneUpdate or false) 70 | RemoveAnimDict(animDictionary) 71 | end 72 | 73 | return supv.playAnim -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /imports/promise/shared.lua: -------------------------------------------------------------------------------- 1 | local function Async(func) 2 | local p = promise.new() 3 | CreateThread(function() 4 | func(function(value) 5 | p:resolve(value) 6 | end, function(err) 7 | p:reject(err) 8 | end) 9 | end) 10 | return p 11 | end 12 | 13 | local function PromiseNew(func) 14 | local p = Async(func) 15 | return setmetatable({}, { 16 | __index = { 17 | Then = function(self, fulfilled) 18 | p = p:next(fulfilled) 19 | return self 20 | end, 21 | Catch = function(self, rejected) 22 | p = p:next(nil, rejected) 23 | return self 24 | end 25 | } 26 | }) 27 | end 28 | 29 | return { 30 | async = Async, -- credit: linden @overextended 31 | new = PromiseNew -- credit: SUP2Ak, for people love javascript x) 32 | } 33 | 34 | ---@exemple supv.promise.async (you need to use `CreateThread` function and supv.await method) 35 | ---@important If method is reject then you got error: path/to/file.lua:line: `value you put in reject` 36 | --[[ 37 | CreateThread(function() 38 | local v = supv.await(supv.promise.async(function(resolve, reject) 39 | local x = math.random(1, 100) 40 | if x > 50 then reject(":(") end 41 | resolve(":)") 42 | end)) 43 | 44 | if v then -- only if resolve 45 | print(v) 46 | end 47 | end) 48 | --]] 49 | 50 | ---@exemple supv.promise.new (this method use callback when promise is resolve or reject) 51 | ---@important For this method you need to use `Then` and `Catch` methods, and you don't need to use this method in `CreateThread` function 52 | --[[ 53 | supv.promise.new(function(resolve, reject) 54 | local v = math.random(1, 100) 55 | if v > 50 then reject(':(') end 56 | resolve(':)') 57 | end):Then(function(result) 58 | print('then', result) 59 | end):Catch(function(result) 60 | print('catch', result) 61 | end) 62 | --]] -------------------------------------------------------------------------------- /imports/raycast/client.lua: -------------------------------------------------------------------------------- 1 | local GetShapeTestResultIncludingMaterial = GetShapeTestResultIncludingMaterial 2 | local GetWorldCoordFromScreenCoord = GetWorldCoordFromScreenCoord 3 | local StartShapeTestLosProbe = StartShapeTestLosProbe 4 | 5 | ---@alias Flags 6 | ---| 0 None 7 | ---| 1 Intersect world 8 | ---| 2 Intersect vehicles 9 | ---| 4 Intersect peds simple collision 10 | ---| 8 Intersect peds 11 | ---| 16 Intersect objects 12 | ---| 32 Intersect water 13 | ---| 128 Unknown 14 | ---| 256 Intersect foliage (trees, plants, etc.) 15 | ---| 511 Intersect everything 16 | ---| 4294967295 Intersect everything 17 | 18 | ---@alias TestIgnore 19 | ---| 1 GLASS 20 | ---| 2 SEE_THROUGH 21 | ---| 3 GLASS | SEE_THROUGH 22 | ---| 4 NO_COLLISION 23 | ---| 7 GLASS | SEE_THROUGH | NO_COLLISION 24 | 25 | ---@class Raycast 26 | ---@field hit boolean 27 | ---@field entityHit number 28 | ---@field endCoords vector3 29 | ---@field surfaceNormal vector3 30 | ---@field materialHash number 31 | 32 | ---@param distance number-10? defaults to 10. 33 | ---@param flags number-511? defaults to 511 (https://docs.fivem.net/natives/?_0x377906D8A31E5586). 34 | ---@param ignore TestIgnore-4? TestIgnore.NO_COLLISION 35 | ---@param h vector3? headingFromCoords 36 | ---@return table Raycast { hit: boolean, entityHit: number, endCoords: vector3, surfaceNormal: vector3, materialHash: number} 37 | return function(distance, flags, ignore, h) 38 | local worldVector , normalVector = GetWorldCoordFromScreenCoord(.5, .5) -- Center of the screenX and screenY 39 | local destinationVector = worldVector + normalVector * (distance or 10) 40 | local handle = StartShapeTestLosProbe(worldVector.x, worldVector.y, worldVector.z, destinationVector.x, destinationVector.y, destinationVector.z, flags or 511, supv.cache.ped or 0, ignore or 4) 41 | 42 | while true do 43 | Wait(0) 44 | local retval , hit , endCoords , surfaceNormal , materialHash , entityHit = GetShapeTestResultIncludingMaterial(handle) 45 | if retval ~= 1 then 46 | return { 47 | hit = hit, 48 | entityHit = entityHit, 49 | endCoords = endCoords, 50 | surfaceNormal = surfaceNormal, 51 | materialHash = materialHash, 52 | heading = h and supv.math.headingFromCoords(h, endCoords) 53 | } 54 | end 55 | end 56 | end -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | --]] -------------------------------------------------------------------------------- /imports/string/shared.lua: -------------------------------------------------------------------------------- 1 | local string , math = string, math 2 | 3 | --- supv.string.starts 4 | ---@param str string 5 | ---@param start number 6 | ---@return boolean 7 | local function Starts(str, start) 8 | return string.sub(str, 1, string.len(start)) == start 9 | end 10 | 11 | --- supv.string.firstToUpper 12 | ---@param str string 13 | ---@return string 14 | local function FirstToUpper(str) 15 | str = str:lower() 16 | return str:gsub("^%l", string.upper) 17 | end 18 | 19 | --- supv.string.random 20 | ---@param length? number-70 21 | ---@param data? table 22 | ---@return string 23 | local function RandomString(length, data) 24 | local output, l, default, characterSet = "", length or 70, { 25 | upperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26 | lowerCase = "abcdefghijklmnopqrstuvwxyz", 27 | numbers = "0123456789", 28 | symbols = "!@#$%&()*+-,./:;<=>?^[]{}" 29 | }, '' 30 | 31 | if next(data) then 32 | for k in pairs(data) do 33 | characterSet = characterSet..default[k] 34 | end 35 | else 36 | for _,v in pairs(default) do 37 | characterSet = characterSet..v 38 | end 39 | end 40 | 41 | for i = 1, l do 42 | local random = math.random(#characterSet) 43 | output = output..characterSet:sub(random, random) 44 | end 45 | 46 | return output 47 | end 48 | 49 | local function GenerateUUID() 50 | local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' 51 | return template:gsub('[xy]', function(repl) 52 | local v = (repl == 'x') and math.random(0, 0xf) or math.random(8, 0xb) 53 | return ('%x'):format(v) 54 | end) 55 | end 56 | 57 | return { 58 | starts = Starts, 59 | random = RandomString, 60 | firstToUpper = FirstToUpper, 61 | uuid = GenerateUUID 62 | } -------------------------------------------------------------------------------- /imports/table/shared.lua: -------------------------------------------------------------------------------- 1 | local type , pairs , getmetatable , setmetatable = type, pairs, getmetatable, setmetatable 2 | 3 | --- supv.table.clone - Clone a table 4 | ---@param t table 5 | ---@return table 6 | local function Clone(t) 7 | if type(t) ~= 'table' then return end 8 | local metatable, target = getmetatable(t), {} 9 | 10 | for k,v in pairs(metatable) do 11 | if type(v) == 'table' then 12 | target[k] = Clone(v) 13 | else 14 | target[k] = v 15 | end 16 | end 17 | 18 | setmetatable(target, metatable) 19 | 20 | return target 21 | end 22 | 23 | --- supv.table.contains - Check if a table contains a value(s) 24 | ---@param t table 25 | ---@param value any 26 | ---@return boolean 27 | local function Contains(tbl, value) 28 | if type(value) ~= 'table' then 29 | for _, v in pairs(tbl) do 30 | if v == value then return true end 31 | end 32 | else 33 | local matched_values = 0 34 | local values = 0 35 | for _, v1 in pairs(value) do 36 | values += 1 37 | 38 | for _, v2 in pairs(tbl) do 39 | if v1 == v2 then matched_values += 1 end 40 | end 41 | end 42 | if matched_values == values then return true end 43 | end 44 | 45 | return false 46 | end 47 | 48 | local function toVec3(coords) 49 | local _type = type(coords) 50 | 51 | if _type ~= 'vector3' then 52 | if _type == 'table' or _type == 'vector4' then 53 | return vec3(coords[1] or coords.x, coords[2] or coords.y, coords[3] or coords.z) 54 | end 55 | 56 | error(("expected type 'vector3' or 'table' (received %s)"):format(_type)) 57 | end 58 | 59 | return coords 60 | end 61 | 62 | ---@param coords vector3|table 63 | ---@param heading? number 64 | ---@return vector4 65 | local function toVec4(coords, heading) 66 | local _type = type(coords) 67 | 68 | if _type ~= 'vector4' then 69 | if _type == 'table' or _type == 'vector3' then 70 | return vec4(coords[1] or coords.x, coords[2] or coords.y, coords[3] or coords.z, coords[4] or coords.w or coords.h or heading) 71 | end 72 | 73 | error(("expected type 'vector4' or 'table' (received %s)"):format(_type)) 74 | end 75 | 76 | return coords 77 | end 78 | 79 | return { 80 | clone = Clone, 81 | contains = Contains 82 | toVec3 = toVec3, 83 | toVec4 = toVec4 84 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /imports/timeout/shared.lua: -------------------------------------------------------------------------------- 1 | local SetTimeout = Citizen.SetTimeout 2 | local Timeout = {} 3 | 4 | ---@param cb function 5 | ---@param time integer 6 | ---@return number 7 | local function Set(cb, time) 8 | local id = #Timeout + 1 9 | 10 | SetTimeout(time, function() 11 | if not Timeout[id] then return end 12 | cb() 13 | end) 14 | 15 | Timeout[id] = true 16 | return id 17 | end 18 | 19 | ---@param id number 20 | local function Clear(id) 21 | if Timeout[id] then 22 | Timeout[id] = nil 23 | end 24 | end 25 | 26 | local function Play(self, newCb, newTime) 27 | if newCb then 28 | self.cb = newCb 29 | end 30 | 31 | if newTime then 32 | self.time = newTime 33 | end 34 | 35 | self.played = true 36 | SetTimeout(self.time, function() 37 | if not self.played then return end 38 | self.callback() 39 | end) 40 | end 41 | 42 | local function Cancel(self) 43 | if self.played then 44 | self.played = false 45 | end 46 | end 47 | 48 | local function Remove(self) 49 | Timeout[self.id] = nil 50 | return nil 51 | end 52 | 53 | ---@param data { time: integer, callback: function } 54 | ---@return table 55 | local function New(data) 56 | local self = {} 57 | 58 | self.id = #Timeout + 1 59 | self.time = data?.time or 1000 60 | self.callback = data?.callback or function() end 61 | self.played = false 62 | 63 | self.play = Play 64 | self.cancel = Cancel 65 | self.remove = Remove 66 | 67 | return self 68 | end 69 | 70 | return { 71 | new = New, 72 | set = Set, 73 | clear = Clear, 74 | } 75 | 76 | --[[ 77 | 78 | Exemple : classic 79 | 80 | -- SetTimeout 81 | local timeout = supv.timeout.set(function() 82 | print('timeout passed after 10s') 83 | end, 10000) 84 | 85 | 86 | -- ClearTimeout 87 | supv.timeout.clear(timeout) 88 | 89 | Exemple : class 90 | 91 | local timeout = supv.timeout.new({ 92 | time = 10000, 93 | callback = function() 94 | print('timeout passed after 10s') 95 | end 96 | }) 97 | 98 | -- Play 99 | timeout:play() 100 | 101 | -- Play with new callback & timer 102 | timeout:play(function() 103 | print('timeout passed after 5s') 104 | end, 5000) 105 | 106 | -- Cancel 107 | timeout:cancel() 108 | 109 | -- Remove 110 | timeout = timeout:remove() 111 | --]] -------------------------------------------------------------------------------- /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/version/server.lua: -------------------------------------------------------------------------------- 1 | local PerformHttpRequest , GetResourceMetadata = PerformHttpRequest, GetResourceMetadata 2 | local version = GetResourceMetadata(supv.env, 'version', 0) 3 | 4 | --- supv.version.check 5 | ---@param url string 6 | ---@param webhook? table 7 | ---@param timer? number 8 | ---@param isBeta? boolean 9 | ---@param perso? fun(resp: table) 10 | local function Checker(url, webhook, timer, isBeta, perso) 11 | local message = json.decode(LoadResourceFile(supv.name, ('locales/%s.json'):format(supv.lang))) 12 | 13 | local from = url 14 | url = from == 'tebex' and ("https://raw.githubusercontent.com/SUP2Ak/version-tebex-script/main/%s.json"):format(supv.env) or from == 'github' and ('https://api.github.com/repos/SUP2Ak/%s/releases/latest'):format(supv.env) or url 15 | 16 | CreateThread(function() 17 | Wait(timer or 2500) 18 | 19 | PerformHttpRequest(url, function(status, resp) 20 | if status ~= 200 then return print(message.error_update) end 21 | 22 | resp = json.decode(resp) 23 | -- print(json.encode(resp, {indent=true})) 24 | 25 | if from == 'github' or from == 'tebex' then 26 | local lastVersion = from == 'github' and resp.tag_name or from == 'tebex' and resp.version 27 | lastVersion = lastVersion:gsub('v', '') 28 | 29 | if isBeta then 30 | lastVersion = lastVersion:gsub('b', '') 31 | end 32 | 33 | local _version = {('.'):strsplit(version)} 34 | local _lastVersion = {('.'):strsplit(lastVersion)} 35 | 36 | for i = 1, #_version do 37 | local current, minimum = tonumber(_version[i]), tonumber(_lastVersion[i]) 38 | 39 | if current ~= minimum then 40 | if current < minimum then 41 | print('^9---------------------------------------------------------') 42 | print(message.need_update:format(supv.env, version, lastVersion, from == 'github' and resp.html_url or from == 'tebex' and resp.link)) 43 | print('^9---------------------------------------------------------') 44 | return 45 | else break end 46 | end 47 | end 48 | else 49 | if perso then 50 | perso(resp) 51 | end 52 | end 53 | end, 'GET') 54 | end) 55 | end 56 | 57 | return { 58 | check = Checker 59 | } -------------------------------------------------------------------------------- /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 = GetGameTimer() 14 | 15 | while value == nil do Wait(0) 16 | 17 | local timer = 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 -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | --]] -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /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 = folders[i] 6 | local files = require(('modules.%s.index'):format(folder)) 7 | 8 | if files.shared then 9 | local t = files.shared 10 | for j = 1, #t do 11 | local file = 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 = files[supv.service] 18 | for j = 1, #t do 19 | local file = t[j] 20 | require(('modules.%s.%s.%s'):format(folder, supv.service, file)) 21 | end 22 | end 23 | end 24 | 25 | folders = nil -------------------------------------------------------------------------------- /modules/main/client/cache.lua: -------------------------------------------------------------------------------- 1 | -- redm only 2 | local GetMount = supv.game == 'redm' and GetMount 3 | local IsPedOnMount = supv.game == 'redm' and IsPedOnMount 4 | -- fivem & redm 5 | local PlayerPedId = PlayerPedId 6 | local PlayerId = PlayerId 7 | local GetPlayerServerId = GetPlayerServerId 8 | local GetVehiclePedIsIn = GetVehiclePedIsIn 9 | local GetPedInVehicleSeat = GetPedInVehicleSeat 10 | local GetVehicleMaxNumberOfPassengers = GetVehicleMaxNumberOfPassengers 11 | local GetCurrentPedWeapon = GetCurrentPedWeapon 12 | local TriggerEvent = TriggerEvent 13 | 14 | local cache = _ENV.cache 15 | function cache:set(key, value) 16 | if (self[key] == nil) or (self[key] ~= value) then 17 | self[key] = value 18 | TriggerEvent(('cache:%s'):format(key), value) 19 | return true 20 | end 21 | end 22 | 23 | CreateThread(function() 24 | cache:set('playerid', PlayerId()) 25 | cache:set('serverid', GetPlayerServerId(cache.playerid)) 26 | local RefreshGfxToNui 27 | while true do 28 | cache:set('ped', PlayerPedId()) 29 | 30 | if supv.game == 'redm' then 31 | cache:set('mount', IsPedOnMount(cache.ped) == true and GetMount(cache.ped) or false) 32 | end 33 | 34 | local hasWeapon, weaponHash = GetCurrentPedWeapon(cache.ped, true) 35 | cache:set('weapon', hasWeapon and weaponHash or false) 36 | 37 | local vehicle = GetVehiclePedIsIn(cache.ped, false) 38 | if vehicle > 0 then 39 | cache:set('vehicle', vehicle) 40 | 41 | if not cache.seat or GetPedInVehicleSeat(vehicle, cache.seat) ~= cache.ped then 42 | for i = -1, GetVehicleMaxNumberOfPassengers(vehicle) - 1 do 43 | if GetPedInVehicleSeat(vehicle, i) == cache.ped then 44 | cache:set('seat', i) 45 | break 46 | end 47 | end 48 | end 49 | else 50 | cache:set('vehicle', false) 51 | cache:set('seat', false) 52 | end 53 | 54 | if RefreshGfxToNui then 55 | RefreshGfxToNui() 56 | elseif not RefreshGfxToNui and _ENV.RefreshGfxToNui then 57 | RefreshGfxToNui = _ENV.RefreshGfxToNui 58 | end 59 | 60 | Wait(500) 61 | end 62 | end) 63 | 64 | 65 | function supv.getCache(key) 66 | return cache[key] or key == 'vehicle' and false or false 67 | end -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /modules/main/index.lua: -------------------------------------------------------------------------------- 1 | return { 2 | client = { 3 | 'cache', 4 | --'resource' 5 | }, 6 | 7 | -- server = { 8 | --'resource' 9 | -- } 10 | } -------------------------------------------------------------------------------- /modules/nui/client/action.lua: -------------------------------------------------------------------------------- 1 | ---@class Text2dProps 2 | ---@field title string 3 | ---@field description? string 4 | ---@field keybind? string | 'E' 5 | ---@field title2? string 6 | ---@field description2? string 7 | ---@field keybind2? string 8 | 9 | local visible, currentTitle, currentDescription, currentTitle2 = false 10 | 11 | ---@param data Text2dProps 12 | function supv.showText2D(data) 13 | if data.description and currentDescription == data.description then 14 | return 15 | end 16 | 17 | if not data.description and data.title == currentTitle then 18 | return 19 | end 20 | 21 | if not data.title2 then 22 | currentTitle2 = nil 23 | end 24 | 25 | supv.sendReactMessage(true, { 26 | action = 'supv_core:action:send', 27 | data = { 28 | title = data.title, 29 | description = data.description, 30 | keybind = data.keybind or 'E', 31 | title2 = data.title2 or nil, 32 | description2 = data.title2 and data.description2 or nil, 33 | keybind2 = data.title2 and data.keybind2 or nil, 34 | } 35 | }) 36 | 37 | visible, currentTitle, currentDescription, currentTitle2 = true, data.title, data.description, data.title2 38 | end 39 | 40 | ---@param force? boolean 41 | function supv.hideText2D(force) 42 | if not visible and not force then return end 43 | 44 | supv.sendReactMessage(true, { 45 | action = 'supv_core:action:hide', 46 | }) 47 | 48 | visible, currentTitle, currentDescription, currentTitle2 = false, nil, nil, nil 49 | end 50 | 51 | ---@return boolean 52 | ---@return string? 53 | ---@return string? 54 | function supv.isText2D() 55 | return visible, currentTitle, currentDescription 56 | end -------------------------------------------------------------------------------- /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) ]] -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /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/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.1.0", 4 | "homepage": "web/build", 5 | "private": true, 6 | "dependencies": { 7 | "@emotion/react": "^11.11.3", 8 | "@fortawesome/fontawesome-svg-core": "^6.5.1", 9 | "@fortawesome/free-brands-svg-icons": "^6.5.1", 10 | "@fortawesome/free-regular-svg-icons": "^6.5.1", 11 | "@fortawesome/free-solid-svg-icons": "^6.5.1", 12 | "@fortawesome/react-fontawesome": "fortawesome/react-fontawesome", 13 | "@mantine/core": "^6.0.9", 14 | "@mantine/dates": "^6.0.9", 15 | "@mantine/form": "^7.5.0", 16 | "@mantine/hooks": "^7.5.1", 17 | "@mantine/modals": "^6.0.9", 18 | "@mantine/notifications": "^6.0.9", 19 | "@testing-library/jest-dom": "^6.4.1", 20 | "@testing-library/react": "^14.2.1", 21 | "@testing-library/user-event": "^14.5.2", 22 | "@types/jest": "^29.5.12", 23 | "@types/node": "^20.11.16", 24 | "@types/react": "^18.2.48", 25 | "@types/react-dom": "^18.2.18", 26 | "@types/react-syntax-highlighter": "^15.5.11", 27 | "dayjs": "^1.11.10", 28 | "emoji-picker-react": "^4.7.10", 29 | "moment": "^2.30.1", 30 | "prop-types": "^15.8.1", 31 | "react": "^18.2.0", 32 | "react-dom": "^18.2.0", 33 | "react-icons": "^5.0.1", 34 | "react-markdown": "^9.0.1", 35 | "react-scripts": "^5.0.1", 36 | "react-syntax-highlighter": "^15.5.0", 37 | "remark-gfm": "^4.0.0", 38 | "typescript": "^5.3.3", 39 | "web-vitals": "^3.5.2" 40 | }, 41 | "scripts": { 42 | "start": "cross-env PUBLIC_URL=/ craco start", 43 | "start:game": "cross-env IN_GAME_DEV=1 craco start", 44 | "build": "rimraf build && craco build", 45 | "test": "craco test", 46 | "eject": "react-scripts eject" 47 | }, 48 | "eslintConfig": { 49 | "extends": [ 50 | "react-app", 51 | "react-app/jest" 52 | ] 53 | }, 54 | "browserslist": { 55 | "production": [ 56 | ">0.2%", 57 | "not dead", 58 | "not op_mini all" 59 | ], 60 | "development": [ 61 | "last 1 chrome version", 62 | "last 1 firefox version", 63 | "last 1 safari version" 64 | ] 65 | }, 66 | "devDependencies": { 67 | "@craco/craco": "^7.1.0", 68 | "cross-env": "^7.0.3", 69 | "rimraf": "^5.0.5" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | NUI React Boilerplate 8 | 9 | 10 | 11 |
12 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /web/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | MantineProvider, 4 | ColorSchemeProvider, 5 | ColorScheme, 6 | } from "@mantine/core"; 7 | import { useClipboard } from '@mantine/hooks'; 8 | import { themeOverride } from "./theme"; 9 | import { useNuiEvent } from "./hooks/useNuiEvent"; 10 | 11 | import CrosshairTool from "./features/tool/Crosshair"; 12 | import Crosshair from "./features/crosshair/Crosshair"; 13 | //import {useConfig} from './providers/ConfigProvider'; // TODO: use config 14 | import { isEnvBrowser } from "./utils/misc"; 15 | import ConvertUnixTime from "./features/tool/ConvertUnix"; 16 | //import DialogComponent from './features/dialog/Dialog'; 17 | import NotificationsWrapper from "./features/notify/SimpleNotifyWrapp"; 18 | import ModalConfirm from "./features/modal/ModalConfirm"; 19 | import ModalCustom from "./features/modal/ModalCustom"; 20 | import ActionWrapper from "./features/action/ActionWrapper"; 21 | import BillingComponent from "./features/billing/Billing"; 22 | //import ResourceManager from "./features/resource/main"; 23 | //import ChatText from './features/chat/Chat'; 24 | 25 | import DevTool from "./dev/DevEnv"; 26 | 27 | 28 | const App: React.FC = () => { 29 | //const { config } = useConfig(); // TODO: use config 30 | const [colorScheme, setColorScheme] = useState("dark"); 31 | const toggleColorScheme = (value?: ColorScheme) => 32 | setColorScheme(value || (colorScheme === "dark" ? "light" : "dark")); 33 | const clipboard = useClipboard({ timeout: 500 }); 34 | 35 | useNuiEvent("supv_core:copy", (data) => { 36 | clipboard.copy(data); 37 | }); 38 | 39 | return ( 40 | <> 41 | 45 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {/* 59 | */} 60 | {isEnvBrowser() && } 61 | 62 | 63 | 64 | ); 65 | }; 66 | 67 | export default App; -------------------------------------------------------------------------------- /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/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/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/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/dev/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './notifications'; 2 | export * from './emojipicker'; -------------------------------------------------------------------------------- /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/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 | }; -------------------------------------------------------------------------------- /web/src/dev/debug/billing.ts: -------------------------------------------------------------------------------- 1 | import { debugData } from "../../utils/debugData"; 2 | 3 | 4 | // Selection / Custom 5 | const AmendesOptions = { 6 | options: [ 7 | { label: "Amende 1", value: 'amende_1'}, 8 | { label: "Amende 2", value: 'amende_2'}, 9 | { label: "Amende 3", value: 'amende_3'}, 10 | { label: 'Custom', value: 'custom'} 11 | ], 12 | filter: { // [value] is important to match the options, so "id" is the same as "value" 13 | amende_1: {label: "Amende 1", amount: 100, id: 'amende_1'}, 14 | amende_2: {label: "Amende 2", amount: 200, id: 'amende_2'}, 15 | amende_3: {label: "Amende 3", amount: 300, id: 'amende_3'}, 16 | } 17 | }; 18 | 19 | // Multi Slection 20 | const ItemActions = { 21 | options: [ 22 | { label: "Pain", value: 'bread'}, 23 | { label: "Milk", value: 'milk'}, 24 | { label: "Eggs", value: 'eggs'}, 25 | { label: 'Burger', value: 'burger'}, 26 | { label: 'Pizza', value: 'pizza' } 27 | ], 28 | filter: { 29 | bread: {label: "Pain", amount: 100, id: 'bread'}, 30 | milk: {label: "Milk", amount: 200, id: 'milk'}, 31 | eggs: {label: "Eggs", amount: 300, id: 'eggs'}, 32 | burger: {label: "Burger", amount: 400, id: 'burger'}, 33 | pizza: {label: "Pizza", amount: 500, id: 'pizza'} 34 | } 35 | } 36 | 37 | const BuildBilling = { 38 | amende: { 39 | type: "amende", 40 | options: AmendesOptions.options, 41 | amendesOptions: AmendesOptions.filter, 42 | articlesOptions: AmendesOptions.filter, 43 | canRemise: false, 44 | nom: "Zoulette", 45 | prenom: "Yoyo" 46 | //remise: { min: 1, max: 10 } 47 | }, 48 | custom: { 49 | type: "custom", 50 | options: ItemActions.options, 51 | amendesOptions: ItemActions.filter, 52 | articlesOptions: ItemActions.filter, 53 | canRemise: true, 54 | remise: { min: 1, max: 10 } 55 | }, 56 | normal: { 57 | type: "normal", 58 | canRemise: true, 59 | remise: { min: 1, max: 10 } 60 | }, 61 | item_service: { 62 | type: "item_service", 63 | options: ItemActions.options, 64 | articlesOptions: ItemActions.filter, 65 | canRemise: true, 66 | remise: { min: 1, max: 10 } 67 | } 68 | } 69 | 70 | 71 | export const debugBilling = async (value: string) => { 72 | debugData([ 73 | { 74 | action: "supv_core:billing:open", 75 | data: BuildBilling[value as keyof typeof BuildBilling], 76 | }, 77 | ]); 78 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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/dev/debug/dialog.ts: -------------------------------------------------------------------------------- 1 | /* 2 | import { debugData } from "../../utils/debugData"; 3 | import type {ModalProps} from "../../typings"; 4 | 5 | export const debugDialog = () => { 6 | debugData([ 7 | { 8 | action: 'supv:modal:opened', 9 | data: { 10 | type: 'confirm', 11 | title: 'Dialog title', 12 | subtitle: 'Mon code snippet', 13 | description:` 14 | A paragraph with *emphasis* and **strong importance**. 15 | > A block quote with ~strikethrough~ and a URL: https://reactjs.org. 16 | 17 | * Lists 18 | * [ ] todo 19 | * [x] done 20 | 21 | A table: 22 | 23 | | a | b | 24 | | - | - | 25 | ` 26 | } as ModalProps, 27 | } 28 | ]); 29 | } 30 | 31 | /* 32 | /*debugData([ 33 | { 34 | action: 'supv:dialog:opened', 35 | data: { 36 | /*title: 'Dialog title', 37 | subtitle: 'Lorem Ipsum', 38 | description: 'Contrary to popular.', 39 | } as DialogProps, 40 | } 41 | ]);*/ 42 | 43 | /*` 44 | ~~~tsx 45 | return ( 46 | <> 47 | 48 | setOpened(true)} 52 | style={{ position: 'fixed', bottom: 20, right: 20, zIndex: 1000 }} 53 | > 54 | 55 | 56 | 57 | 58 | setOpened(false)} title="tool dev" padding="md" size={300}> 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ); 67 | ~~~ 68 | `,*/ -------------------------------------------------------------------------------- /web/src/dev/debug/modals/confirm.ts: -------------------------------------------------------------------------------- 1 | import { debugData } from "../../../utils/debugData"; 2 | import type { ModalConfirmProps } from "../../../typings"; 3 | /* 4 | const desc = ` 5 | A paragraph with *emphasis* and **strong importance**. 6 | > A block quote with ~strikethrough~ and a URL: https://reactjs.org. 7 | 8 | * Lists 9 | * [ ] todo 10 | * [x] done 11 | 12 | A table: 13 | 14 | | a | b | 15 | | - | - | 16 | `; 17 | 18 | const code = ` 19 | ~~~js 20 | const a = 1; 21 | const b = 2; 22 | const c = a + b; 23 | ~~~ 24 | `;*/ 25 | 26 | const text = "blablabla"; 27 | 28 | export const debugModalsConfirm = () => { 29 | debugData([ 30 | { 31 | action: 'supv:modal:opened-confirm', 32 | data: { 33 | title: 'Dialog title', 34 | description: text, 35 | transition: { 36 | name: 'skew-up', 37 | duration: 200, 38 | timingFunction: 'ease-in-out' 39 | }, 40 | } as ModalConfirmProps, 41 | } 42 | ]); 43 | } 44 | 45 | /*import type {ModalProps} from "../../../typings"; 46 | const desc = ` 47 | A paragraph with *emphasis* and **strong importance**. 48 | > A block quote with ~strikethrough~ and a URL: https://reactjs.org. 49 | 50 | * Lists 51 | * [ ] todo 52 | * [x] done 53 | 54 | A table: 55 | 56 | | a | b | 57 | | - | - | 58 | `; 59 | 60 | export const debugModalsConfirm = () => { 61 | debugData([ 62 | { 63 | action: 'sl:modal:opened', 64 | data: { 65 | type: 'confirm', 66 | title: 'Dialog title', 67 | subtitle: 'Markdown', 68 | //style: { 69 | //backgroundImage: `linear-gradient(to left bottom, rgba(39,54,102,0.38), rgba(36,51,98,0.38), rgba(34,48,94,0.38), rgba(31,46,90,0.38), rgba(29,43,86,0.//38), rgba(31,40,81,0.38), rgba(32,36,75,0.//38), rgba2,33,70,0.38), rgba(34,29,62,0.38), rgba(34,24,54,0.38), rgba(33,21,46,0.38), rgba(31,17,39,0.38))`, 70 | //transition: 'all 0.3s ease-in-out', 71 | //color: '#0FBA81', 72 | //borderRadius: '5px', 73 | //boxShadow: '0 0 10px 0 rgba(0,0,0,0.2)', 74 | //}, 75 | description: desc 76 | } as ModalProps, 77 | } 78 | ]); 79 | }*/ -------------------------------------------------------------------------------- /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/billing/_components/amende.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Select, NumberInput, TextInput } from "@mantine/core"; 3 | import { MathRound } from "../../../utils"; 4 | 5 | interface AmendesProps { 6 | data: any; 7 | options: Array<{ label: string; value: string | number }>; 8 | amendesOptions: { [key: number]: { label: string, amount: number, id: number } }; 9 | //onChanged: (key: string, value: any) => void; 10 | setData: (data: any) => void; 11 | } 12 | 13 | export const Amendes: React.FC = ({ 14 | data, 15 | options, 16 | amendesOptions, 17 | setData, 18 | //onChanged, 19 | }) => { 20 | return ( 21 | <> 22 | onChanged(index, value as string, data?.required, data?.callback) } 24 | error={props.error || false} 25 | styles={(theme) => ({ 26 | item: { 27 | '&[data-selected]': { 28 | color: 'white', 29 | background: 'rgba(14, 44, 100, 0.86)', //theme.colors.blue[6], 30 | '&, &:hover': { 31 | color: 'white', 32 | background: 'rgba(14, 44, 100, 0.86)' 33 | }, 34 | }, 35 | '&[data-hovered]': { 36 | //color: theme.colors.blue[8], 37 | background: 'rgba(14, 44, 100, 0.86)', 38 | }, 39 | //color: theme.colors.gray[1], 40 | }, 41 | })} 42 | /> 43 | 44 | ); 45 | }; -------------------------------------------------------------------------------- /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/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/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/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 |
13 | 14 | Y 15 | 16 | 17 | Accepter 18 | 19 | | 20 | 21 | Refuser 22 | 23 | 24 | N 25 | 26 |
27 | ); 28 | }; -------------------------------------------------------------------------------- /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 |
17 | {source && ( 18 | 27 | )} 28 | 29 | {description} 30 | 31 |
32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /web/src/features/notify/components/_title.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supv-cfx/supv_core/a0b2f7ac30251061f8ea9d676990ade2324df16f/web/src/features/notify/components/_title.tsx -------------------------------------------------------------------------------- /web/src/features/notify/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './_action'; 2 | //export * from './_title'; 3 | export * from './_description'; -------------------------------------------------------------------------------- /web/src/features/resource/components/_array_switch.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Box, Switch, Text } from "@mantine/core"; 3 | import { ButtonsEditor } from "./_buttons"; 4 | import { CanAddInputEditor } from "./_canAdd"; 5 | import { _ArraySwitchEditorProps } from "../../../typings"; 6 | 7 | export const ArraySwitch: React.FC<_ArraySwitchEditorProps> = ({ 8 | inputKey, 9 | label, 10 | description, 11 | currentValue, 12 | resource, 13 | file, 14 | navKey, 15 | groupLabel, 16 | index, 17 | setResourceData, 18 | canAdd, 19 | addOption, 20 | }) => { 21 | const [isDisabled, setIsDisabled] = useState(true); 22 | const [value, setValue] = useState>(currentValue); 23 | return ( 24 | 32 | {label && ( 33 | 34 | {label} 35 | 36 | )} 37 | {description && ( 38 | 39 | {description} 40 | 41 | )} 42 | {value.map((v, i) => ( 43 | { 49 | const newValue = [...value]; 50 | newValue[i] = e.currentTarget.checked; 51 | setValue(newValue); 52 | }} 53 | size="xs" 54 | m={10} 55 | /> 56 | ))} 57 | {canAdd && ( 58 | 64 | )} 65 | 76 | 77 | ); 78 | }; 79 | -------------------------------------------------------------------------------- /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/features/resource/components/_buttons.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { ActionIcon, Group } from "@mantine/core"; 3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 4 | import { 5 | faPenToSquare, 6 | faCheck, 7 | faXmark, 8 | } from "@fortawesome/free-solid-svg-icons"; 9 | import { fetchNui } from "../../../utils/fetchNui"; 10 | import { _ButtonEditorProps } from "../../../typings"; 11 | 12 | export const ButtonsEditor: React.FC<_ButtonEditorProps> = ({ 13 | inputKey, 14 | resource, 15 | file, 16 | value, 17 | setIsDisabled, 18 | isDisabled, 19 | setResourceData, 20 | navKey, 21 | index, 22 | }) => { 23 | const [isHovered, setIsHovered] = useState(0); 24 | 25 | return ( 26 | 27 | setIsHovered(1)} 32 | onMouseLeave={() => setIsHovered(0)} 33 | onClick={(e) => { 34 | !isDisabled && setIsDisabled(true); 35 | !isDisabled && 36 | setResourceData(resource, file, value, navKey, index); 37 | const k = !isDisabled && inputKey.replace(`${resource}.`, ""); 38 | !isDisabled && 39 | fetchNui("supv:rm:validate", { 40 | resource: resource, 41 | file: file, 42 | key: k, 43 | value: value, 44 | }); 45 | }} 46 | > 47 | 48 | 49 | setIsHovered(2)} 54 | onMouseLeave={() => setIsHovered(0)} 55 | onClick={(e) => setIsDisabled(false)} 56 | > 57 | 58 | 59 | setIsHovered(3)} 65 | onMouseLeave={() => setIsHovered(0)} 66 | onClick={(e) => { 67 | !isDisabled && setIsDisabled(true); 68 | }} 69 | > 70 | 71 | 72 | 73 | ); 74 | }; 75 | -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /web/src/features/resource/components/_input.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { TextInput, Box } from "@mantine/core"; 3 | import { ButtonsEditor } from "./_buttons"; 4 | import { _StringEditorProps } from "../../../typings"; 5 | 6 | export const InputEdit: React.FC<_StringEditorProps> = ({ 7 | inputKey, 8 | label, 9 | description, 10 | placeholder, 11 | defaultValue, 12 | currentValue, 13 | resource, 14 | file, 15 | navKey, 16 | index, 17 | setResourceData 18 | }) => { 19 | const [isDisabled, setIsDisabled] = useState(true); 20 | const [value, setValue] = useState(currentValue || defaultValue); 21 | 22 | return ( 23 | 31 | {setValue(e.currentTarget.value)}} 39 | m={5} 40 | /> 41 | 52 | 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /web/src/features/resource/components/_object_string.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Box, TextInput, Text } from "@mantine/core"; 3 | import { ButtonsEditor } from "./_buttons"; 4 | import { _ObjectInputEditorProps } from "../../../typings"; 5 | 6 | export const ObjectString: React.FC<_ObjectInputEditorProps> = ({ 7 | inputKey, 8 | label, 9 | description, 10 | currentValue, 11 | resource, 12 | file, 13 | placeHolders, 14 | navKey, 15 | index, 16 | setResourceData, 17 | }) => { 18 | const [isDisabled, setIsDisabled] = useState(true); 19 | const [value, setValue] = useState>(currentValue); 20 | 21 | return ( 22 | 30 | {label && ( 31 | 32 | {label} 33 | 34 | )} 35 | {Object.keys(value).map((v: string, i: number) => ( 36 | { 44 | const newValue = { ...value }; 45 | newValue[v] = e.currentTarget.value; 46 | setValue(newValue); 47 | }} 48 | size="xs" 49 | m={10} 50 | /> 51 | ))} 52 | 63 | 64 | ); 65 | }; -------------------------------------------------------------------------------- /web/src/features/resource/components/_object_switch.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Box, Switch, Text } from "@mantine/core"; 3 | import { ButtonsEditor } from "./_buttons"; 4 | import { _ObjectSwitchEditorProps } from "../../../typings"; 5 | 6 | export const ObjectSwitch: React.FC<_ObjectSwitchEditorProps> = ({ 7 | inputKey, 8 | label, 9 | description, 10 | currentValue, 11 | resource, 12 | file, 13 | navKey, 14 | index, 15 | setResourceData, 16 | }) => { 17 | const [isDisabled, setIsDisabled] = useState(true); 18 | const [value, setValue] = useState>(currentValue); 19 | 20 | return ( 21 | 29 | {label && ( 30 | 31 | {label} 32 | 33 | )} 34 | {description && ( 35 | 36 | {description} 37 | 38 | )} 39 | 40 | {Object.keys(value).map((v: string, i: number) => ( 41 | { 47 | const newValue = { ...value }; 48 | newValue[v] = e.currentTarget.checked; 49 | setValue(newValue); 50 | }} 51 | size="xs" 52 | m={10} 53 | /> 54 | ))} 55 | 56 | 67 | 68 | ); 69 | }; 70 | -------------------------------------------------------------------------------- /web/src/features/resource/components/_switch.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Box, Switch } from "@mantine/core"; 3 | import { ButtonsEditor } from "./_buttons"; 4 | import { _BooleanSwitchProps } from "../../../typings"; 5 | 6 | export const BooleanEdit: React.FC<_BooleanSwitchProps> = ({ 7 | inputKey, 8 | label, 9 | description, 10 | currentValue, 11 | resource, 12 | file, 13 | navKey, 14 | index, 15 | setResourceData 16 | }) => { 17 | const [isDisabled, setIsDisabled] = useState(true); 18 | const [value, setValue] = useState(currentValue); 19 | 20 | return ( 21 | 29 | { 36 | setValue(e.currentTarget.checked); 37 | }} 38 | size="xs" 39 | m={5} 40 | /> 41 | 52 | 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /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' -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /web/src/hooks/useNuiEvent.ts: -------------------------------------------------------------------------------- 1 | import {MutableRefObject, useEffect, useRef} from "react"; 2 | import {noop} from "../utils/misc"; 3 | 4 | interface NuiMessageData { 5 | action: string; 6 | data: T; 7 | } 8 | 9 | type NuiHandlerSignature = (data: T) => void; 10 | 11 | /** 12 | * A hook that manage events listeners for receiving data from the client scripts 13 | * @param action The specific `action` that should be listened for. 14 | * @param handler The callback function that will handle data relayed by this hook 15 | * 16 | * @example 17 | * useNuiEvent<{visibility: true, wasVisible: 'something'}>('setVisible', (data) => { 18 | * // whatever logic you want 19 | * }) 20 | * 21 | **/ 22 | 23 | export const useNuiEvent = ( 24 | action: string, 25 | handler: (data: T) => void 26 | ) => { 27 | const savedHandler: MutableRefObject> = useRef(noop); 28 | 29 | // Make sure we handle for a reactive handler 30 | useEffect(() => { 31 | savedHandler.current = handler; 32 | }, [handler]); 33 | 34 | useEffect(() => { 35 | const eventListener = (event: MessageEvent>) => { 36 | const { action: eventAction, data } = event.data; 37 | 38 | if (savedHandler.current) { 39 | if (eventAction === action) { 40 | savedHandler.current(data); 41 | } 42 | } 43 | }; 44 | 45 | window.addEventListener("message", eventListener); 46 | // Remove Event Listener on component cleanup 47 | return () => window.removeEventListener("message", eventListener); 48 | }, [action]); 49 | }; 50 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /web/src/providers/ConfigProvider.tsx: -------------------------------------------------------------------------------- 1 | import { Context, createContext, useContext, useEffect, useState } from 'react'; 2 | import { MantineColor } from '@mantine/core'; 3 | import { fetchNui } from '../utils/fetchNui'; 4 | import { 5 | NotificationConfigProviderProps, 6 | EmojiPickerProps 7 | } from '../typings'; 8 | import { 9 | NotificationConfigDev, 10 | ConfigEmojiPicker 11 | } from '../dev/config'; 12 | 13 | interface Config { 14 | primaryColor: MantineColor; 15 | primaryShade: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; 16 | notificationStyles: NotificationConfigProviderProps; 17 | emojiPicker: EmojiPickerProps; 18 | } 19 | 20 | interface ConfigCtxValue { 21 | config: Config; 22 | setConfig: (config: Config) => void; 23 | } 24 | 25 | const ConfigCtx = createContext<{ config: Config; setConfig: (config: Config) => void } | null>(null); 26 | 27 | const ConfigProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { 28 | const [config, setConfig] = useState({ 29 | primaryColor: 'blue', 30 | primaryShade: 6, 31 | notificationStyles: NotificationConfigDev, 32 | emojiPicker: ConfigEmojiPicker, 33 | }); 34 | 35 | useEffect(() => { 36 | fetchNui('supv:react:getConfig').then((data) => setConfig(data)); 37 | }, []); 38 | 39 | return {children}; 40 | }; 41 | 42 | export const useConfig = () => useContext(ConfigCtx as Context); 43 | export default ConfigProvider; -------------------------------------------------------------------------------- /web/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /web/src/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { MantineThemeOverride } from '@mantine/core'; 2 | 3 | export const themeOverride: MantineThemeOverride = { 4 | fontFamily: 'Sarabun', 5 | } -------------------------------------------------------------------------------- /web/src/typings/ConvertUnix.ts: -------------------------------------------------------------------------------- 1 | export interface ConvertUnixProps { 2 | unix_time: number; 3 | format_date?: string; 4 | } -------------------------------------------------------------------------------- /web/src/typings/Crosshair.tsx: -------------------------------------------------------------------------------- 1 | export interface CrosshairProps { 2 | show_cross: boolean; 3 | alpha: number; 4 | color: number; 5 | color_b: number; 6 | color_r: number; 7 | color_g: number; 8 | dot: number; 9 | gap: number; 10 | cap: "round" | "butt" | "square"; 11 | size: number; 12 | style: number; 13 | useAlpha: number; 14 | thickness: number; 15 | fixedGap: number; 16 | outlineThickness: number; 17 | drawOutline: number; 18 | dot_useAlpha: number; 19 | dot_alpha: number; 20 | dot_color: number; 21 | dot_color_b: number; 22 | dot_color_r: number; 23 | dot_color_g: number; 24 | dot_thickness: number; 25 | dot_size: number; 26 | dot_style: number; 27 | dot_drawOutline: number; 28 | dot_outlineThickness: number; 29 | dot_outAlpha: number; 30 | dot_outColor: number; 31 | dot_outColor_b: number; 32 | dot_outColor_r: number; 33 | dot_outColor_g: number; 34 | } 35 | 36 | export const CrosshairDefault: CrosshairProps = { 37 | show_cross: true, 38 | alpha: 200, 39 | color: 3, 40 | color_b: 50, 41 | color_r: 50, 42 | color_g: 250, 43 | dot: 1, 44 | gap: 3, 45 | size: 12, 46 | style: 3, 47 | cap: "butt", 48 | useAlpha: 1, 49 | thickness: 1.6, 50 | fixedGap: 0, 51 | outlineThickness: 0, 52 | drawOutline: 0, 53 | dot_useAlpha: 1, 54 | dot_alpha: 200, 55 | dot_color: 3, 56 | dot_color_b: 50, 57 | dot_color_r: 50, 58 | dot_color_g: 250, 59 | dot_thickness: 0.5, 60 | dot_size: 4, 61 | dot_style: 3, 62 | dot_drawOutline: 1, 63 | dot_outlineThickness: 16, 64 | dot_outAlpha: 200, 65 | dot_outColor: 4, 66 | dot_outColor_b: 50, 67 | dot_outColor_r: 50, 68 | dot_outColor_g: 250, 69 | }; 70 | -------------------------------------------------------------------------------- /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/typings/InputDate.ts: -------------------------------------------------------------------------------- 1 | export type InputDateProps = { 2 | 3 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | */ -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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'; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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'; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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/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 | --------------------------------------------------------------------------------