From d4f7fd563f035111603c4462f9120a6721ed463e Mon Sep 17 00:00:00 2001 From: solareon <769465+solareon@users.noreply.github.com> Date: Sun, 30 Jun 2024 20:25:33 +0200 Subject: [PATCH] refactor: use unique ids for groups --- .github/workflows/debugbuild.yml | 61 +++++++++ .github/workflows/release-action.yml | 2 +- {server/bridge => bridge}/esx.lua | 2 + {server/bridge => bridge}/nd.lua | 2 + {server/bridge => bridge}/ox.lua | 2 + {server/bridge => bridge}/qb.lua | 2 + {server/bridge => bridge}/qbx.lua | 2 + client/main.lua | 104 +++++--------- fxmanifest.lua | 9 +- server/api.lua | 195 +++++++++++++++------------ server/groups.lua | 12 +- server/main.lua | 54 +++++--- ui/src/App.tsx | 18 +-- ui/src/components/DataHandler.tsx | 18 ++- ui/src/components/GroupDashboard.tsx | 6 +- ui/src/components/GroupJob.tsx | 5 +- ui/src/components/PlayerList.tsx | 124 ++++++++--------- ui/src/storage/PlayerDataStore.ts | 4 +- ui/src/utils/fetchReactNui.ts | 38 ------ ui/tailwind.config.js | 24 ++-- 20 files changed, 362 insertions(+), 322 deletions(-) create mode 100644 .github/workflows/debugbuild.yml rename {server/bridge => bridge}/esx.lua (89%) rename {server/bridge => bridge}/nd.lua (88%) rename {server/bridge => bridge}/ox.lua (87%) rename {server/bridge => bridge}/qb.lua (91%) rename {server/bridge => bridge}/qbx.lua (89%) delete mode 100644 ui/src/utils/fetchReactNui.ts diff --git a/.github/workflows/debugbuild.yml b/.github/workflows/debugbuild.yml new file mode 100644 index 0000000..eef3ac7 --- /dev/null +++ b/.github/workflows/debugbuild.yml @@ -0,0 +1,61 @@ +name: "Debug Build Action" + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to run the workflow on' + required: true + default: 'main' + +jobs: + debug-build: + name: "Create Debug Build" + runs-on: "ubuntu-latest" + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.branch }} + + - name: Install ZIP + run: sudo apt install zip + + - name: Install pnpm + uses: pnpm/action-setup@v4.0.0 + with: + version: 9 + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: "pnpm" + cache-dependency-path: "ui/pnpm-lock.yaml" + + - name: Install dependencies + run: pnpm i --frozen-lockfile + working-directory: ui + + - name: Run build + run: pnpm build + working-directory: ui + env: + CI: false + + - name: Bundle files + run: | + shopt -s extglob + mkdir -p ./temp/${{ github.event.repository.name }} + mkdir -p ./temp/${{ github.event.repository.name }}/ui + cp ./{README.md,LICENSE,fxmanifest.lua} ./temp/${{ github.event.repository.name }} + cp -r ./{client,bridge,server} ./temp/${{ github.event.repository.name }} + cp -r ./ui/public ./temp/${{ github.event.repository.name }}/ui/public + cd ./temp && zip -r ../${{ github.event.repository.name }}.zip ./${{ github.event.repository.name }} + + - name: Upload Build Artifact + uses: actions/upload-artifact@v4 + with: + name: debug-build + path: ${{ github.event.repository.name }}.zip diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index a691619..e883dca 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -47,7 +47,7 @@ jobs: mkdir -p ./temp/${{ github.event.repository.name }} mkdir -p ./temp/${{ github.event.repository.name }}/ui cp ./{README.md,LICENSE,fxmanifest.lua} ./temp/${{ github.event.repository.name }} - cp -r ./{client,server} ./temp/${{ github.event.repository.name }} + cp -r ./{client,bridge,server} ./temp/${{ github.event.repository.name }} cp -r ./ui/public ./temp/${{ github.event.repository.name }}/ui/public cd ./temp && zip -r ../${{ github.event.repository.name }}.zip ./${{ github.event.repository.name }} diff --git a/server/bridge/esx.lua b/bridge/esx.lua similarity index 89% rename from server/bridge/esx.lua rename to bridge/esx.lua index b67825e..b629021 100644 --- a/server/bridge/esx.lua +++ b/bridge/esx.lua @@ -1,5 +1,7 @@ if GetResourceState('es_extended') ~= 'started' then return end +if not IsDuplicityVersion() then return end + local ESX = exports.es_extended:getSharedObject() local function getPlayer(source) diff --git a/server/bridge/nd.lua b/bridge/nd.lua similarity index 88% rename from server/bridge/nd.lua rename to bridge/nd.lua index d9ab182..d9dbae8 100644 --- a/server/bridge/nd.lua +++ b/bridge/nd.lua @@ -1,5 +1,7 @@ if not lib.checkDependency('ND_Core', '2.0.0') then return end +if not IsDuplicityVersion() then return end + NDCore = {} lib.load('@ND_Core.init') diff --git a/server/bridge/ox.lua b/bridge/ox.lua similarity index 87% rename from server/bridge/ox.lua rename to bridge/ox.lua index a3eb657..80a62fe 100644 --- a/server/bridge/ox.lua +++ b/bridge/ox.lua @@ -1,5 +1,7 @@ if GetResourceState('ox_core') ~= 'started' then return end +if not IsDuplicityVersion() then return end + local Ox = require '@ox_core.lib.init' ---Get the player data from the source diff --git a/server/bridge/qb.lua b/bridge/qb.lua similarity index 91% rename from server/bridge/qb.lua rename to bridge/qb.lua index 1b30c32..187c6bd 100644 --- a/server/bridge/qb.lua +++ b/bridge/qb.lua @@ -1,5 +1,7 @@ if GetResourceState('qb-core') ~= 'started' or GetResourceState('qbx_core') == 'started' then return end +if not IsDuplicityVersion() then return end + local QBCore = exports['qb-core']:GetCoreObject() function GetPlayerData(source) diff --git a/server/bridge/qbx.lua b/bridge/qbx.lua similarity index 89% rename from server/bridge/qbx.lua rename to bridge/qbx.lua index abf4751..77ff29d 100644 --- a/server/bridge/qbx.lua +++ b/bridge/qbx.lua @@ -1,5 +1,7 @@ if GetResourceState('qbx_core') ~= 'started' then return end +if not IsDuplicityVersion() then return end + function GetPlayerData(source) return exports.qbx_core:GetPlayer(source).PlayerData end diff --git a/client/main.lua b/client/main.lua index fd33bff..3f6c43f 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,5 +1,21 @@ local identifier = 'slrn_groups' +local function sendCustomAppMessage(action, data) + exports['lb-phone']:SendCustomAppMessage( + identifier, { + action = action, + data = data + }) +end + +local function sendNotification(message, title) + exports['lb-phone']:SendNotification({ + app = identifier, + title = title or nil, + content = message, + }) +end + CreateThread(function() while GetResourceState('lb-phone') ~= 'started' do Wait(500) @@ -16,6 +32,14 @@ CreateThread(function() -- ui = "http://localhost:3000", -- for local ui build testing icon = "https://cfx-nui-" .. GetCurrentResourceName() .. "/ui/public/icon.svg", fixBlur = true, + onUse = function() + lib.callback('slrn_groups:server:getSetupAppData', false, function(setupAppData) + sendCustomAppMessage('setupApp', setupAppData) + if setupAppData.groupStatus == 'IN_PROGRESS' then + sendCustomAppMessage('startJob', {}) + end + end) + end, images = { -- OPTIONAL array of screenshots of the app, used for showcasing the app "https://cfx-nui-" .. GetCurrentResourceName() .. "/ui/public/screenshot-light.png", "https://cfx-nui-" .. GetCurrentResourceName() .. "/ui/public/screenshot-dark.png" @@ -43,30 +67,7 @@ RegisterNuiCallback('getPlayerData', function(_, cb) end) RegisterNuiCallback('getGroupData', function(_, cb) - local groups, inGroup, groupStatus, groupStages = lib.callback.await('slrn_groups:server:getAllGroups') - if groups then - cb(groups) - end - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'setInGroup', - data = inGroup or false - }) - local groupData = lib.callback.await('slrn_groups:server:getGroupMembersNames', false) - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'setCurrentGroup', - data = groupData or {} - }) - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'setGroupJobSteps', - data = groupStages or {} - }) - if groupStatus == 'IN_PROGRESS' then - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'startJob', - data = {} - }) - end - cb({}) + --noop end) RegisterNuiCallback('getGroupJobSteps', function(_, cb) @@ -81,31 +82,19 @@ end) RegisterNuiCallback('joinGroup', function(data, cb) local message = lib.callback.await('slrn_groups:server:joinGroup', false, data) - - exports['lb-phone']:SendNotification({ - app = identifier, - content = message, - }) + sendNotification(message) cb({}) end) RegisterNuiCallback('leaveGroup', function(_, cb) local message = lib.callback.await('slrn_groups:server:leaveGroup') - - exports['lb-phone']:SendNotification({ - app = identifier, - content = message, - }) + sendNotification(message) cb({}) end) RegisterNuiCallback('deleteGroup', function(_, cb) local message = lib.callback.await('slrn_groups:server:deleteGroup') - - exports['lb-phone']:SendNotification({ - app = identifier, - content = message, - }) + sendNotification(message) cb({}) end) @@ -115,43 +104,22 @@ RegisterNUICallback('getMemberList', function(_, cb) end) RegisterNUICallback('removeGroupMember', function (data, cb) - TriggerServerEvent('slrn_groups:server:removeGroupMember', data) - cb({}) -end) - -RegisterNUICallback('promoteGroupMember', function (data, cb) - TriggerServerEvent('slrn_groups:server:promoteGroupMember', data) + local message = lib.callback.await('slrn_groups:server:removeGroupMember', false, data) + sendNotification(message) cb({}) end) RegisterNetEvent('slrn_groups:client:refreshGroups', function(groupData) local currentGroupData, inGroup = lib.callback.await('slrn_groups:server:getGroupMembersNames', false) - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'setCurrentGroup', - data = currentGroupData or {} - }) - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'setInGroup', - data = inGroup or 0 - }) - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'setGroups', - data = groupData, - }) + sendCustomAppMessage('setCurrentGroup', currentGroupData or {}) + sendCustomAppMessage('setInGroup', inGroup or false) + sendCustomAppMessage('setGroups', groupData) end) RegisterNetEvent('slrn_groups:client:updateGroupStage', function(_, stage) - groupStage = stage - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'setGroupJobSteps', - data = stage - }) + sendCustomAppMessage('setGroupJobSteps', stage) end) RegisterNetEvent('slrn_groups:client:CustomNotification', function(header, msg) - exports['lb-phone']:SendNotification({ - app = identifier, - title = header, - content = msg, - }) -end) + sendNotification(msg, header) +end) \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua index 5b1b22c..b71faf3 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -11,10 +11,15 @@ repository 'https://github.com/solareon/slrn_groups' client_scripts { 'client/**/*', - '@qbx_core/modules/playerdata.lua' -- remove if using qb-core but you really should switch to qbox } + server_script 'server/**/*' -shared_script '@ox_lib/init.lua' + +shared_scripts { + '@ox_lib/init.lua', + 'bridge/*.lua' +} + files { "ui/dist/**/*", diff --git a/server/api.lua b/server/api.lua index b611659..c9b0d80 100644 --- a/server/api.lua +++ b/server/api.lua @@ -6,32 +6,45 @@ local groups = {} local api = {} +local groupIndex = 0 + +--- Returns the group table index by ID +---@param id number +---@return group? +function api.findGroupById(id) + for i=1, #groups do + if groups[i].id == id then + return groups[i] + end + end +end + --- Notifies all members of a group with a message ----@param groupID number +---@param groupId number ---@param msg string ---@param type string -function api.NotifyGroup(groupID, msg, type) - local group = groups[groupID] +function api.NotifyGroup(groupId, msg, type) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('NotifyGroup was sent an invalid groupID :'..groupID) + return lib.print.error('NotifyGroup was sent an invalid groupId :'..groupId) end for i = 1, #group.members do - utils.notify(group.members[i].Player, msg, type) + utils.notify(group.members[i].playerId, msg, type) end end utils.exportHandler('NotifyGroup', api.NotifyGroup) --- Notifies all members of a group with a custom notification ----@param groupID number +---@param groupId number ---@param header string ---@param msg string -function api.pNotifyGroup(groupID, header, msg) - local group = groups[groupID] +function api.pNotifyGroup(groupId, header, msg) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('pNotifyGroup was sent an invalid groupID :'..groupID) + return lib.print.error('pNotifyGroup was sent an invalid groupId :'..groupId) end group:triggerGroupEvent('slrn_groups:client:CustomNotification', header or 'NO HEADER', msg or 'NO MSG') @@ -43,10 +56,10 @@ utils.exportHandler('pNotifyGroup', api.pNotifyGroup) ---@param groupId number ---@param ... any function api.triggerGroupEvent(eventName, groupId, ...) - local group = groups[groupId] + local group = api.findGroupById(groupId) if not group then - return lib.print.error('triggerGroupEvent was sent an invalid groupID :'..groupId) + return lib.print.error('triggerGroupEvent was sent an invalid groupId :'..groupId) end group:triggerGroupEvent(eventName, ...) @@ -67,14 +80,14 @@ utils.exportHandler('triggerGroupEvent', api.triggerGroupEvent) ---@field routeColor number? --- Creates a blip for all members of a group ----@param groupID number +---@param groupId number ---@param name string ---@param data BlipData -function api.CreateBlipForGroup(groupID, name, data) - local group = groups[groupID] +function api.CreateBlipForGroup(groupId, name, data) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('CreateBlipForGroup was sent an invalid groupID :'..groupID) + return lib.print.error('CreateBlipForGroup was sent an invalid groupId :'..groupId) end group:triggerGroupEvent('groups:createBlip', name, data) @@ -82,13 +95,13 @@ end utils.exportHandler('CreateBlipForGroup', api.CreateBlipForGroup) --- Removes a blip for all members of a group ----@param groupID number +---@param groupId number ---@param name string -function api.RemoveBlipForGroup(groupID, name) - local group = groups[groupID] +function api.RemoveBlipForGroup(groupId, name) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('RemoveBlipForGroup was sent an invalid groupID :'..groupID) + return lib.print.error('RemoveBlipForGroup was sent an invalid groupId :'..groupId) end group:triggerGroupEvent('groups:removeBlip', name) @@ -102,8 +115,8 @@ function api.GetGroupByMembers(src) if src then for i = 1, #groups do for j = 1, #groups[i].members do - if groups[i].members[j].Player == src then - return i + if groups[i].members[j].playerId == src then + return groups[i].id end end end @@ -112,13 +125,13 @@ end utils.exportHandler('GetGroupByMembers', api.GetGroupByMembers) --- Returns the group members of a given group ----@param groupID number +---@param groupId number ---@return number[]? -function api.getGroupMembers(groupID) - local group = groups[groupID] +function api.getGroupMembers(groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('getGroupMembers was sent an invalid groupID :'..groupID) + return lib.print.error('getGroupMembers was sent an invalid groupId :'..groupId) end return group:getGroupMembers() @@ -126,13 +139,13 @@ end utils.exportHandler('getGroupMembers', api.getGroupMembers) --- Returns the number of members in a given group ----@param groupID number +---@param groupId number ---@return number? -function api.getGroupSize(groupID) - local group = groups[groupID] +function api.getGroupSize(groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('getGroupSize was sent an invalid groupID :'..groupID) + return lib.print.error('getGroupSize was sent an invalid groupId :'..groupId) end return #group.members @@ -140,13 +153,13 @@ end utils.exportHandler('getGroupSize', api.getGroupSize) --- Returns the leader of a group ----@param groupID number +---@param groupId number ---@return number? -function api.GetGroupLeader(groupID) - local group = groups[groupID] +function api.GetGroupLeader(groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('GetGroupLeader was sent an invalid groupID :'..groupID) + return lib.print.error('GetGroupLeader was sent an invalid groupId :'..groupId) end return group.leader @@ -154,37 +167,42 @@ end utils.exportHandler('GetGroupLeader', api.GetGroupLeader) --- Destroys a group and removes it from the array ----@param groupID number -function api.DestroyGroup(groupID) - local group = groups[groupID] +---@param groupId number +function api.DestroyGroup(groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('DestroyGroup was sent an invalid groupID :'..groupID) + return lib.print.error('DestroyGroup was sent an invalid groupId :'..groupId) end -- If more than just the leader is in the group, notify all members that the group has been disbanded if #group.members > 1 then for i = 1, #group.members do - local source = group.members[i].Player + local source = group.members[i].playerId if source ~= group.leader then - utils.notify(group.members[i].Player, 'The group has been disbanded', 'error') + utils.notify(group.members[i].playerId, 'The group has been disbanded', 'error') end end end - table.remove(groups, groupID) + for i = 1, #groups do + if groups[i].id == groupId then + table.remove(groups, i) + break + end + end - TriggerEvent('slrn_groups:server:GroupDeleted', groupID, group:getGroupMembers()) + TriggerEvent('slrn_groups:server:GroupDeleted', groupId, group:getGroupMembers()) lib.triggerClientEvent('slrn_groups:client:refreshGroups', -1, api.GetAllGroups()) end utils.exportHandler('DestroyGroup', api.DestroyGroup) -function api.AddMember(groupID, source) - local group = groups[groupID] +function api.AddMember(groupId, source) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('AddMember was sent an invalid groupID :'..groupID) + return lib.print.error('AddMember was sent an invalid groupId :'..groupId) end group:addMember(source) @@ -201,7 +219,7 @@ function api.isPasswordCorrect(groupId, password) local group = groups?[groupId] if not group then - return lib.print.error('isPasswordCorrect was sent an invalid groupID :'..groupId) + return lib.print.error('isPasswordCorrect was sent an invalid groupId :'..groupId) end return group:getPassword() == password @@ -211,13 +229,13 @@ end --- Checks if the player is the leader in the group ---@param src number ----@param groupID number +---@param groupId number ---@return boolean? -function api.isGroupLeader(src, groupID) - local group = groups[groupID] +function api.isGroupLeader(src, groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('isGroupLeader was sent an invalid groupID :'..groupID) + return lib.print.error('isGroupLeader was sent an invalid groupId :'..groupId) end return group.leader == src @@ -227,13 +245,13 @@ utils.exportHandler('isGroupLeader', api.isGroupLeader) ---Removes a player from a group ---@param source number ----@param groupID number +---@param groupId number ---@return boolean? -function api.RemovePlayerFromGroup(source, groupID) - local group = groups[groupID] +function api.RemovePlayerFromGroup(source, groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('RemovePlayerFromGroup was sent an invalid groupID :'..groupID) + return lib.print.error('RemovePlayerFromGroup was sent an invalid groupId :'..groupId) end @@ -241,14 +259,14 @@ function api.RemovePlayerFromGroup(source, groupID) for i = 1, memberCount do local member = group.members[i] - if member.Player == source then + if member.playerId == source then table.remove(group.members, i) lib.triggerClientEvent('slrn_groups:client:refreshGroups', -1, api.GetAllGroups()) -- There are no more members in the group, destroy it if memberCount == 1 then - api.DestroyGroup(groupID) + api.DestroyGroup(groupId) end return true @@ -259,14 +277,14 @@ function api.RemovePlayerFromGroup(source, groupID) end --- Sets the group status and stages ----@param groupID number +---@param groupId number ---@param status 'WAITING' | 'IN_PROGRESS' | 'DONE' ---@param stages {id: number, name: string, isDone: boolean}[] -function api.setJobStatus(groupID, status, stages) - local group = groups[groupID] +function api.setJobStatus(groupId, status, stages) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('setJobStatus was sent an invalid groupID :'..groupID) + return lib.print.error('setJobStatus was sent an invalid groupId :'..groupId) end group.status = status @@ -277,13 +295,13 @@ end utils.exportHandler('setJobStatus', api.setJobStatus) --- Returns the group status ----@param groupID number +---@param groupId number ---@return 'WAITING' | 'IN_PROGRESS' | 'DONE' | nil -function api.getJobStatus(groupID) - local group = groups[groupID] +function api.getJobStatus(groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('getJobStatus was sent an invalid groupID :'..groupID) + return lib.print.error('getJobStatus was sent an invalid groupId :'..groupId) end return group.status @@ -291,12 +309,12 @@ end utils.exportHandler('getJobStatus', api.getJobStatus) --- Resets the group status and stages ----@param groupID number -function api.resetJobStatus(groupID) - local group = groups[groupID] +---@param groupId number +function api.resetJobStatus(groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('resetJobStatus was sent an invalid groupID :'..groupID) + return lib.print.error('resetJobStatus was sent an invalid groupId :'..groupId) end group.status = 'WAITING' @@ -307,13 +325,13 @@ end utils.exportHandler('resetJobStatus', api.resetJobStatus) --- Returns the group current stages ----@param groupID number +---@param groupId number ---@return {id: number, name: string, isDone: boolean}[]? -function api.GetGroupStages(groupID) - local group = groups[groupID] +function api.GetGroupStages(groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('GetGroupStages was sent an invalid groupID :'..groupID) + return lib.print.error('GetGroupStages was sent an invalid groupId :'..groupId) end return group.stage @@ -321,13 +339,13 @@ end utils.exportHandler('GetGroupStages', api.GetGroupStages) --- Returns whether or not the group is created by a script ----@param groupID number +---@param groupId number ---@return boolean? -function api.isGroupTemp(groupID) - local group = groups[groupID] +function api.isGroupTemp(groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('isGroupTemp was sent an invalid groupID :'..groupID) + return lib.print.error('isGroupTemp was sent an invalid groupId :'..groupId) end return group.ScriptCreated or false @@ -352,10 +370,10 @@ utils.exportHandler('getAllGroups', api.GetAllGroups) ---@param groupId number ---@return string[]? function api.GetGroupMembersNames(groupId) - local group = groups[groupId] + local group = api.findGroupById(groupId) if not group then - return lib.print.error('GetGroupMembersNames was sent an invalid groupID :'..groupId) + return lib.print.error('GetGroupMembersNames was sent an invalid groupId :'..groupId) end local members = {} @@ -365,8 +383,8 @@ function api.GetGroupMembersNames(groupId) members[i] = { name = member.name, - Player = member.Player, - isLeader = member.Player == group.leader + playerId = member.playerId, + isLeader = member.playerId == group.leader } end @@ -375,21 +393,21 @@ end utils.exportHandler('GetGroupMembersNames', api.GetGroupMembersNames) --- Changes the leader of a group ----@param groupID number +---@param groupId number ---@return boolean? -function api.ChangeGroupLeader(groupID) - local group = groups[groupID] +function api.ChangeGroupLeader(groupId) + local group = api.findGroupById(groupId) if not group then - return lib.print.error('ChangeGroupLeader was sent an invalid groupID :'..groupID) + return lib.print.error('ChangeGroupLeader was sent an invalid groupId :'..groupId) end local members = group.members if #members > 1 then for i = 1, #members do - if members[i].Player ~= group.leader then - group.leader = members[i].Player + if members[i].playerId ~= group.leader then + group.leader = members[i].playerId return true end end @@ -404,14 +422,13 @@ end ---@param password string? ---@return number function api.CreateGroup(src, name, password) - local id = #groups + 1 + groupIndex = groupIndex + 1 + local id = groupIndex local group = group_class:new(id, name, password, src, true) - groups[id] = group + groups[#groups + 1] = group - lib.print.error('created group') - lib.print.error(api.GetAllGroups()) -- Send non-sensitive data to all clients (id, name, memberCount) lib.triggerClientEvent('slrn_groups:client:refreshGroups', -1, api.GetAllGroups()) diff --git a/server/groups.lua b/server/groups.lua index d9b4a4d..2958e50 100644 --- a/server/groups.lua +++ b/server/groups.lua @@ -5,7 +5,7 @@ ---@field public ScriptCreated boolean ---@field public status string ---@field public stage {id: number, name: string, isDone: boolean}[] ----@field public members {name: string, CID: number, Player: number}[] +---@field public members {name: string, playerId: number}[] ---@field private private {password: string} local groups = lib.class('groups') @@ -26,7 +26,7 @@ function groups:constructor(id, name, password, leader, ScriptCreated) self.members = { { name = GetPlayerName(leader), - Player = leader + playerId = leader } } self.stage = {} @@ -39,7 +39,7 @@ end function groups:addMember(source) self.members[#self.members+1] = { name = GetPlayerName(source), - Player = source + playerId = source } end @@ -59,7 +59,7 @@ function groups:refreshGroupStages() -- #TODO: remove the need of doing this, just alert that something changed and whenever they open the app do a callback? for i=1, #self.members do if self.members[i] then - local source = self.members[i].Player + local source = self.members[i].playerId TriggerClientEvent('slrn_groups:client:updateGroupStage', source, self.status, self.stage) TriggerClientEvent('slrn_groups:client:refreshGroups', source, self:getClientData(), true) @@ -74,7 +74,7 @@ function groups:getGroupMembers() local members = {} for i = 1, #self.members do - members[i] = self.members[i].Player + members[i] = self.members[i].playerId end return members @@ -96,7 +96,7 @@ function groups:triggerGroupEvent(eventName, ...) local payloadLen = #payload for i = 1, #self.members do - TriggerClientEventInternal(eventName, self.members[i].Player --[[@as string]], payload, payloadLen) + TriggerClientEventInternal(eventName, self.members[i].playerId --[[@as string]], payload, payloadLen) end end diff --git a/server/main.lua b/server/main.lua index 7dba53a..835cfff 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,12 +1,12 @@ local api = require 'server.api' -lib.callback.register('slrn_groups:server:removeGroupMember', function(source, data) +lib.callback.register('slrn_groups:server:removeGroupMember', function(source, targetId) local groupId = api.GetGroupByMembers(source) if groupId then if api.isGroupLeader(source, groupId) then - if api.RemovePlayerFromGroup(data.target, groupId) then - return 'Removed Player' + if api.RemovePlayerFromGroup(targetId, groupId) then + return 'Removed '..GetPlayerName(targetId).. ' from the group' end end end @@ -14,6 +14,20 @@ lib.callback.register('slrn_groups:server:removeGroupMember', function(source, d return 'Error removing player' end) +lib.callback.register('slrn_groups:server:promoteGroupMember', function(source, targetId) + local groupId = api.GetGroupByMembers(source) + + if groupId then + if api.isGroupLeader(source, groupId) then + if api.ChangeGroupLeader(groupId, targetId) then + return 'Promoted '..GetPlayerName(targetId).. ' to Leader' + end + end + end + + return 'Error promoting player' +end) + lib.callback.register('slrn_groups:server:deleteGroup', function(source) local groupId = api.GetGroupByMembers(source) @@ -49,10 +63,10 @@ lib.callback.register('slrn_groups:server:joinGroup', function(source, data) end) lib.callback.register('slrn_groups:server:leaveGroup', function(source) - local groupID = api.GetGroupByMembers(source) + local groupId = api.GetGroupByMembers(source) - if groupID then - api.RemovePlayerFromGroup(source, groupID) + if groupId then + api.RemovePlayerFromGroup(source, groupId) return 'Left Group' end @@ -73,8 +87,6 @@ lib.callback.register('slrn_groups:server:getGroupMembers', function(source) local groupId = api.GetGroupByMembers(source) if groupId then - lib.print.error('Getting Group Members') - lib.print.error(api.getGroupMembers(groupId)) return api.getGroupMembers(groupId) end end) @@ -87,25 +99,27 @@ lib.callback.register('slrn_groups:server:getGroupMembersNames', function(source local groupId = api.GetGroupByMembers(source) if groupId then - lib.print.error('Getting Group Members Names') - lib.print.error(api.GetGroupMembersNames(groupId)) return api.GetGroupMembersNames(groupId), groupId or false end end) ----Get all groups +---Get app startup data ---@param source number -lib.callback.register('slrn_groups:server:getAllGroups', function(source) +---@return table +lib.callback.register('slrn_groups:server:getSetupAppData', function(source) local groupId = api.GetGroupByMembers(source) - lib.print.error('Getting All Groups') - lib.print.error(api.GetAllGroups()) - - if groupId then - return api.GetAllGroups(), groupId, api.getJobStatus(groupId), api.GetGroupStages(groupId) - else - return api.GetAllGroups(), false - end + local setupAppData = { + playerData = { + source = source, + }, + groups = api.GetAllGroups() or {}, + inGroup = groupId or false, + groupData = groupId and api.GetGroupMembersNames(groupId) or {}, + groupStages = groupId and api.GetGroupStages(groupId) or {}, + groupStatus = groupId and api.getJobStatus(groupId) or false, + } + return setupAppData end) ---Get group stages diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 6288d0d..a8c11f1 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -24,8 +24,8 @@ const App = () => { onSettingsChange, } = window as any; const [currentPage, setCurrentPage] = useState("GroupDashboard"); - const { inGroup, currentGroup, setGroups, setIsLeader, setCurrentGroup } = useGroupStore(); - const { playerData, setPlayerData } = usePlayerDataStore(); + const { inGroup, currentGroup } = useGroupStore(); + const { playerData } = usePlayerDataStore(); useEffect(() => { if (devMode) { @@ -38,26 +38,12 @@ const App = () => { } }, [theme]); - useEffect(() => { - fetchNui("getPlayerData").then((data) => { - setPlayerData(data); - }); - - fetchNui("getGroupData").then((data) => setGroups(data)); - }, []); - useEffect(() => { if (!inGroup) { setCurrentPage("GroupDashboard"); } }, [inGroup]); - useEffect(() => { - if (currentGroup) { - setIsLeader(currentGroup.some((member) => member.playerId === playerData.source && member.isLeader)); - } - }, [currentGroup]); - useNuiEvent("startJob", () => { setCurrentPage("GroupJob"); }); diff --git a/ui/src/components/DataHandler.tsx b/ui/src/components/DataHandler.tsx index 78433ce..db716a2 100644 --- a/ui/src/components/DataHandler.tsx +++ b/ui/src/components/DataHandler.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { useNuiEvent } from "../hooks/useNuiEvent"; import { usePlayerDataStore } from "../storage/PlayerDataStore"; import { useGroupStore } from "../storage/GroupStore"; @@ -6,7 +6,7 @@ import { useGroupJobStepStore } from "../storage/GroupJobStepStore"; const DataHandler: React.FC = () => { const { setPlayerData } = usePlayerDataStore(); - const { setGroups, setCurrentGroup, setInGroup } = useGroupStore(); + const { setGroups, setIsLeader, setCurrentGroup, setInGroup } = useGroupStore(); const { setGroupJobSteps } = useGroupJobStepStore(); useNuiEvent("setPlayerData", setPlayerData); @@ -25,6 +25,20 @@ const DataHandler: React.FC = () => { ) ); }); + useNuiEvent("setupApp", (data) => { + if (!data) { + console.error("Invalid setupApp data", data); + return; + } + setPlayerData(data.playerData); + setGroups(data.groups); + setCurrentGroup(data.groupData); + setInGroup(data.inGroup); + setGroupJobSteps(data.groupJobSteps); + if (data.groupData && data.playerData) { + setIsLeader(data.groupData.some((member) => member.playerId === data.playerData.source && member.isLeader)); + } + }); return null; // This component doesn't render anything }; diff --git a/ui/src/components/GroupDashboard.tsx b/ui/src/components/GroupDashboard.tsx index 88477e6..4296f14 100644 --- a/ui/src/components/GroupDashboard.tsx +++ b/ui/src/components/GroupDashboard.tsx @@ -107,7 +107,7 @@ const GroupDashboard = ({ setCurrentPage, fetchNui }) => { Leave Group -

+

{currentGroups?.length > 0 ? ( 'Create a group or join an existing group below' ) : ( @@ -116,13 +116,13 @@ const GroupDashboard = ({ setCurrentPage, fetchNui }) => {

{currentGroups && currentGroups.length > 0 && ( <> - {currentGroups.map((group) => { + {currentGroups.map((group, index) => { let isMember = group.id === inGroup; return ( //
= ({ setCurrentPage, fetchNui }) => {
- {groupJobSteps.length > 0 + {groupJobSteps && groupJobSteps.length > 0 ? "Here are the current group tasks" : "No tasks available"}
- {groupJobSteps.map((step, index) => ( + {groupJobSteps && groupJobSteps.map((step, index) => (
{ - const { playerData } = usePlayerDataStore(); - const { currentGroup, isLeader } = useGroupStore(); + const { playerData } = usePlayerDataStore(); + const { currentGroup, isLeader } = useGroupStore(); - const removeGroupMember = (member) => { - fetchNui('removeGroupMember', member.playerId); - }; + const removeGroupMember = (member) => { + fetchNui("removeGroupMember", member.playerId); + }; - const promoteGroupMember = (member) => { - fetchNuie('promoteGroupMember', member.playerId); - } + useEffect(() => { + if (!currentGroup || currentGroup.length === 0) { + onClose(); + } + }, [currentGroup]); - return ( -
-
-
-

Group Members

-

{currentGroup.name}

+ return ( +
+
+
+

Group Members

+

{currentGroup.name}

+
+
+ {currentGroup.map((member, index) => { + return ( +
+ + <> + {isLeader && + member.playerId !== + playerData.source && ( + <> + + removeGroupMember( + member + ) + } + /> + + )} + + {member.name} +
+ ); + })} +
+
+ +
+
-
- {currentGroup.map((member, index) => { - return ( -
- - <> - { (isLeader && member.playerId !== playerData.source) && ( - <> - removeGroupMember(member)} - /> - promoteGroupMember(member)} - /> - - )} - - {member.name} -
- ); - })} -
-
- -
-
-
- ); + ); }; export default PlayerList; diff --git a/ui/src/storage/PlayerDataStore.ts b/ui/src/storage/PlayerDataStore.ts index a727a24..15805ae 100644 --- a/ui/src/storage/PlayerDataStore.ts +++ b/ui/src/storage/PlayerDataStore.ts @@ -11,5 +11,7 @@ interface PlayerDataStore { export const usePlayerDataStore = create((set) => ({ playerData: null, - setPlayerData: (data) => set({ playerData: data }), + setPlayerData: (data) => { + set({ playerData: data }); + }, })); diff --git a/ui/src/utils/fetchReactNui.ts b/ui/src/utils/fetchReactNui.ts deleted file mode 100644 index 63782ce..0000000 --- a/ui/src/utils/fetchReactNui.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { isEnvBrowser } from "./misc"; - -/** - * Simple wrapper around fetch API tailored for CEF/NUI use. This abstraction - * can be extended to include AbortController if needed or if the response isn't - * JSON. Tailor it to your needs. - * - * @param eventName - The endpoint eventname to target - * @param data - Data you wish to send in the NUI Callback - * @param mockData - Mock data to be returned if in the browser - * - * @return returnData - A promise for the data sent back by the NuiCallbacks CB argument - */ - -export async function fetchReactNui( - eventName: string, - data?: unknown, - mockData?: T, -): Promise { - const options = { - method: "post", - headers: { - "Content-Type": "application/json; charset=UTF-8", - }, - body: JSON.stringify(data), - }; - if (isEnvBrowser() && mockData) return mockData; - - const resourceName = (window as any).GetParentResourceName - ? (window as any).GetParentResourceName() - : "nui-frame-app"; - - const resp = await fetch(`https://${resourceName}/${eventName}`, options); - - const respFormatted = await resp.json(); - - return respFormatted; -} diff --git a/ui/tailwind.config.js b/ui/tailwind.config.js index f8dc5d5..021a6d8 100644 --- a/ui/tailwind.config.js +++ b/ui/tailwind.config.js @@ -16,22 +16,22 @@ module.exports = { plugins: [ createThemes({ light: { - text: '#10120c', - background: '#fafbf9', - primary: '#94a276', - secondary: '#a3c1c2', - accent: '#8b9eb1', + text: '#030704', + background: '#fcfdfd', + primary: '#57b277', + secondary: '#a7b7d7', + accent: '#8981c5', danger: '#ff0000', - success: '#00ff00', + success: '#8981c5', }, dark: { - text: '#f1f3ed', - background: '#050604', - primary: '#7b895d', - secondary: '#3d5b5c', - accent: '#4e6174', + text: '#f8fcf9', + background: '#020303', + primary: '#4da86d', + secondary: '#283858', + accent: '#423a7e', danger: '#ff0000', - success: '#00ff00', + success: '#423a7e', }, }), ],