diff --git a/.editorconfig b/.editorconfig index 9c97eb2..463c03f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,4 +6,9 @@ insert_final_newline = false indent_style = space charset = utf-8 trim_trailing_whitespace = true +indent_size = 2 +indent_style = space + +[*.lua] indent_size = 4 +indent_style = space \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..930bc8e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: Main CI +on: [push, pull_request] +jobs: + build: + name: Build Test + runs-on: ubuntu-latest + defaults: + run: + working-directory: ui + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get Commit Range + id: get_commit_range + run: | + if [ "${{ github.event_name }}" == "push" ]; then + echo "commit_range=${{ github.event.before }}...${{ github.sha }}" >> $GITHUB_ENV + else + echo "commit_range=${{ github.event.pull_request.base.sha }}...${{ github.sha }}" >> $GITHUB_ENV + fi + + - name: Check for changes in UI directory + id: check_changes + run: | + if git diff --quiet ${{ env.commit_range }} -- ui; then + echo "No changes in UI directory. Skipping step." + echo "skip_step=true" >> $GITHUB_ENV + else + echo "Changes detected in UI directory. Proceeding with step." + echo "skip_step=false" >> $GITHUB_ENV + fi + + - name: Install pnpm + if: env.skip_step == 'false' + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Setup node environment + if: env.skip_step == 'false' + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: "pnpm" + cache-dependency-path: "ui/pnpm-lock.yaml" + + - name: Install deps + if: env.skip_step == 'false' + run: pnpm install --frozen-lockfile + working-directory: ui + + - name: Try build + if: env.skip_step == 'false' + run: pnpm build + working-directory: ui + env: + CI: true diff --git a/.github/workflows/debugbuild.yml b/.github/workflows/debugbuild.yml index eef3ac7..606e318 100644 --- a/.github/workflows/debugbuild.yml +++ b/.github/workflows/debugbuild.yml @@ -2,11 +2,6 @@ name: "Debug Build Action" on: workflow_dispatch: - inputs: - branch: - description: 'Branch to run the workflow on' - required: true - default: 'main' jobs: debug-build: @@ -51,11 +46,11 @@ jobs: 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 + cp -r ./ui/dist ./temp/${{ github.event.repository.name }}/ui/ 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 + name: ${{ github.event.repository.name }} + path: ./temp/ diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6baaacd..93ef79d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,15 +7,27 @@ jobs: steps: - uses: actions/checkout@v4 with: + fetch-depth: 2 ref: ${{ github.event.pull_request.head.sha }} + - name: Check for lua changes + id: check_changes + run: | + if git diff --quiet HEAD^ HEAD -- '*.lua'; then + echo "No changes to lua files. Skipping step." + echo "skip_step=true" >> $GITHUB_ENV + else + echo "Changes detected to lua files. Proceeding with step." + echo "skip_step=false" >> $GITHUB_ENV + fi - name: Lint + if: env.skip_step == 'false' uses: iLLeniumStudios/fivem-lua-lint-action@v2 with: capture: "junit.xml" args: "-t --formatter JUnit" extra_libs: ox_lib+mysql+qblocales+qbox+qbox_playerdata+qbox_lib - name: Generate Lint Report - if: always() + if: env.skip_step == 'false' uses: mikepenz/action-junit-report@v4 with: report_paths: "**/junit.xml" diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index feecf0b..e883dca 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -19,13 +19,37 @@ jobs: - 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: | - rm -rf ./.github ./.vscode ./.git shopt -s extglob - mkdir ./${{ github.event.repository.name }} - cp -r !(${{ github.event.repository.name }}) ${{ github.event.repository.name }} - zip -r ./${{ github.event.repository.name }}.zip ./${{ github.event.repository.name }} + 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: Get App Token uses: actions/create-github-app-token@v1 diff --git a/README.md b/README.md index acf001c..ff83e7a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # slrn_groups -A group app for [lb-phone](https://lbphone.com/) the utilizes `qb-phone` compatible exports. This should work with most resources that expect a `qb-phone` style group but there might be corner cases. This isn't great code but it works. +A group app for [lb-phone](https://lbphone.com/) the provides `qb-phone` compatible exports. This should work with most resources that expect a `qb-phone` style group. -**QB/QBOX supported with bridge** +**QBOX/QB/ESX/ND/OX supported with bridge** + +![CI](https://github.com/solareon/slrn_groups/workflows/ci.yml/badge.svg) +![Lint](https://github.com/solareon/slrn_groups/workflows/lint.yml/badge.svg) ## Installation Download the [release version](https://github.com/solareon/slrn_groups/releases) and copy to your server. @@ -10,7 +13,8 @@ Download the [release version](https://github.com/solareon/slrn_groups/releases) - [Discord](https://discord.gg/TZFBBHvG6E) # Credits -- [RijayJH](https://github.com/RijayJH/rj_groups-for-lb_phone) for original code +- [FjamZoo](https://github.com/FjamZoo) for rewriting groups layer +- [RijayJH](https://github.com/RijayJH/rj_groups-for-lb_phone) for original idea - [overextended](https://github.com/overextended) for ox_lib # Dependencies @@ -18,66 +22,262 @@ Download the [release version](https://github.com/solareon/slrn_groups/releases) # Exports (server side only) -Both `export.slrn_groups` and `exports['qb-phone']` are supported. +Both `exports.slrn_groups` and `exports['qb-phone']` are supported. + + +# Utility Exports + +## NotifyGroup +This export is used to notify the entire group about specific objectives or events. These are typically ox_lib notifications + +### PARAMETERS +- groupID (string): The identifier of the group. +- message (string): The message to be sent to the group. +- notifyType (string): The type of notification. + +### USAGE ```lua copy -exports.slrn_groups:NotifyGroup(groupID, msg, type) +local group = exports.slrn_groups:GetGroupByMembers(source) +exports.slrn_groups:NotifyGroup(groupID, 'A message', 'success') ``` +## pNotifyGroup + +This export is used to notify the entire group about a specific objective, utilizing the built-in LB-Phone notification type. + +### PARAMETERS +- group (string): The identifier of the group. +- header (string): The header/title of the notification. +- msg (string): The detailed message for the notification. + +### USAGE + ```lua copy +local group = exports.slrn_groups:GetGroupByMembers(source) +local header = "Garbage Job" +local message = "Head to the location marked and pick up trash!" exports.slrn_groups:pNotifyGroup(groupID, header, msg) ``` +## CreateBlipForGroup + +An export to make blips that sync across group members. + +Find all the FiveM related blip data you will need for this here: +https://docs.fivem.net/docs/game-references/blips/ + +### PARAMETERS +- group (string): The identifier of the group. +- name (string): The name/identifier for the blip. +- data (table): The data table containing blip details. + +### USAGE +Do not copy paste this as the blip table might be broken only this as a reference + ```lua copy -exports.slrn_groups:CreateBlipForGroup(groupID, name, data) +local group = exports.slrn_groups:GetGroupByMembers(src) +local blip = { + -- NOTE YOU CAN ONLY USE ONE OF ENTITY NETID RADIUS AS THEY ALL DO THE SAME + -- Picking none of these 3 will make it a normal blip by default + entity = 9421, -- Use this if you have an entity spawned server side + netId = 9421, -- Use this if you have a entity server side and you have the netId + radius = 200, -- How big do you want the radius of the blip to be + + coords = vector4(231, 244, 92, 1.41) -- Just the coords for the blip + color = 49, -- just a red colour + alpha = 255, -- This makes the blip kinda see through if u lower it + sprite = 57, -- Just a circle icon, + scale = 0.7, -- How big do u want the blip to be? + label = "Garbage Route", -- The label for the blip + + -- Route stuff -- + route = true, -- Will set a route for the job (Note a route is not a waypoint they cant remove it) + routeColor = 49, -- just a red to finish off the routeColor +} +exports.slrn_groups:CreateBlipForGroup(group, "Garbage", blip) ``` +## RemoveBlipForGroup + +An export to remove blips across all group members + +### PARAMETERS +- group (string): The identifier of the group. +- name (string): The name/identifier for the blip to be removed. + +### USAGE + ```lua copy -exports.slrn_groups:RemoveBlipForGroup(groupID, name) +local group = exports['qb-phone']:GetGroupByMembers(src) +exports.slrn_groups:RemoveBlipForGroup(group, "Garbage") ``` +## GetGroupByMembers + +An export to get the players current group. If the player has no group it will return nil. + +### PARAMETERS +- src (number): The source ID of the player. + +### USAGE ```lua copy -exports.slrn_groups:GetGroupByMembers(src) +local group = exports.slrn_groups:GetGroupByMembers(src) +print(group) -- Will either print the group ID or print nil. ``` +## getGroupMembers + +An export to get all the group members source ID + +### PARAMETERS +- group (string): The identifier of the group. + +### USAGE ```lua copy -exports.slrn_groups:getGroupMembers(groupID) +local group = exports.slrn_groups:GetGroupByMembers(src) +local members = exports.slrn_groups:getGroupMembers(groupID) +print(json.encode(members)) -- Will print the table for the group members ``` +## getGroupSize + +An export to ge the current group size.\ +This can be used if u want players to be a specific number to start a job. + +### PARAMETERS +- group (string): The identifier of the group. + +### USAGE ```lua copy -exports.slrn_groups:getGroupSize(groupID) +local group = exports.slrn_groups:GetGroupByMembers(src) +local size = exports.slrn_groups:getGroupSize(groupID) +print(size) -- Will print how many people are in the group ``` +## GetGroupLeader +An export to get the current groups leader.\ +This can be used to make sure only the group leader can start a job. + +### PARAMETERS +- group (string): The identifier of the group. + +### USAGE ```lua copy -exports.slrn_groups:GetGroupLeader(groupID) +local group = exports.slrn_groups:GetGroupByMembers(src) +local leader = exports.slrn_groups:GetGroupLeader(group) +print(leader) -- Will print the group leader source ID ``` +## DestroyGroup + +An export to destroy a group. + +### PARAMETERS +- group (string): The identifier of the group. + +### USAGE ```lua copy +local group = exports.slrn_groups:GetGroupByMembers(src) exports.slrn_groups:DestroyGroup(groupID) ``` +## isGroupLeader + +An export to check if a player is the group leader + +### PARAMETERS +- src (number): The source ID of the player. +- group (string): The identifier of the group. + +### USAGE ```lua copy -exports.slrn_groups:isGroupLeader(src, groupID) +local group = exports.slrn_groups:GetGroupByMembers(src) +local leader = exports.slrn_groups:isGroupLeader(src, group) +print(leader) -- Will print true if the source ID is the group leader ``` +## isGroupTemp + +An export to return if a group was created temporarily + +### PARAMETERS +- group (string): The identifier of the group. + +### USAGE ```lua copy -exports.slrn_groups:setJobStatus(groupID, status, stages) +exports.slrn_groups:isGroupTemp(groupID) ``` +## CreateGroup + +An export to create a group by a calling resource + +### PARAMETERS +- src (number): The identifier of the player source. +- name (string): The group name +- password (string): The group password (optional) + +### RETURNS +- groupId (number): id of the group for later use + +### USAGE ```lua copy -exports.slrn_groups:getJobStatus(groupID) +exports.slrn_groups:CreateGroup(src, name, password) ``` +# Job Status + +All exports used to modify or get the group current job statuses, you need to use this to AVOID groups doing 2 jobs at the same time etc. + +## setJobStatus + +An export to set the groups job to something specific. + +### PARAMETERS +- group (string): The identifier of the group. +- status (string): The name/identifier of the job status. +- stages (table): A table containing stages for the job. + +### USAGE ```lua copy -exports.slrn_groups:resetJobStatus(groupID) +local group = exports.slrn_groups:GetGroupByMembers(src) +local Stages = { + [1] = {name = "Head to the area located on your GPS", isDone = false , id = 1}, + [2] = {name = "Find the vehicle I emailed you!", isDone = false , id = 2}, + [3] = {name = "Head to the scrapping location marked on your GPS!", isDone = false , id = 3}, + [4] = {name = "Dissasemble the vehicle for parts!", isDone = false , id = 4}, + [5] = {name = "Break down the rest of the car to get rid of the evidence!", isDone = false , id = 4}, + [6] = {name = "Get out of there before you get seen!", isDone = false , id = 5}, +} +exports.slrn_groups:setJobStatus(group, status, stages) ``` +## getJobStatus + +An export to get the current job the group is doing + +### PARAMETERS +- group (string): The identifier of the group. + +### USAGE ```lua copy -exports.slrn_groups:isGroupTemp(groupID) +local group = exports.slrn_groups:GetGroupByMembers(src) +local job = exports.slrn_groups:getJobStatus(group) +print(job) -- If you followed above step it will print Chop Shop ``` +## resetJobStatus + +An export to reset the current groups job status to be WAITING. + +### PARAMETERS +- group (string): The identifier of the group. + +### USAGE ```lua copy -exports.slrn_groups:CreateGroup(src, name, password) +local group = exports.slrn_groups:GetGroupByMembers(src) +exports.slrn_groups:resetJobStatus(group) ``` # Copyright diff --git a/bridge/esx.lua b/bridge/esx.lua new file mode 100644 index 0000000..b629021 --- /dev/null +++ b/bridge/esx.lua @@ -0,0 +1,17 @@ +if GetResourceState('es_extended') ~= 'started' then return end + +if not IsDuplicityVersion() then return end + +local ESX = exports.es_extended:getSharedObject() + +local function getPlayer(source) + return ESX.GetPlayerFromId(source) +end + +---Get the player data from the source +---@param source number +---@return string +function GetPlayerName(source) + local xPlayer = getPlayer(source) + return xPlayer.getName() +end \ No newline at end of file diff --git a/bridge/nd.lua b/bridge/nd.lua new file mode 100644 index 0000000..d9dbae8 --- /dev/null +++ b/bridge/nd.lua @@ -0,0 +1,19 @@ +if not lib.checkDependency('ND_Core', '2.0.0') then return end + +if not IsDuplicityVersion() then return end + +NDCore = {} + +lib.load('@ND_Core.init') + +function GetPlayer(id) + return NDCore.getPlayer(id) +end + +---Get the player data from the source +---@param source number +---@return string +function GetPlayerName(source) + local player = GetPlayer(source) + return player.fullname +end diff --git a/bridge/ox.lua b/bridge/ox.lua new file mode 100644 index 0000000..80a62fe --- /dev/null +++ b/bridge/ox.lua @@ -0,0 +1,14 @@ +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 +---@param source number +---@return string +function GetPlayerName(source) + local player = Ox.GetPlayer(source) + + return player and player.charId and player.get('name') +end \ No newline at end of file diff --git a/bridge/qb.lua b/bridge/qb.lua new file mode 100644 index 0000000..187c6bd --- /dev/null +++ b/bridge/qb.lua @@ -0,0 +1,18 @@ +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) + local player = QBCore.Functions.GetPlayer(source) + return player.PlayerData +end + +---Get the player data from the source +---@param source number +---@return string +function GetPlayerName(source) + local playerData = GetPlayerData(source) + return playerData.charinfo.firstname .. ' ' .. playerData.charinfo.lastname +end \ No newline at end of file diff --git a/bridge/qbx.lua b/bridge/qbx.lua new file mode 100644 index 0000000..77ff29d --- /dev/null +++ b/bridge/qbx.lua @@ -0,0 +1,15 @@ +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 + +---Get the player data from the source +---@param source number +---@return string +function GetPlayerName(source) + local playerData = GetPlayerData(source) + return playerData.charinfo.firstname .. ' ' .. playerData.charinfo.lastname +end diff --git a/client/functions.lua b/client/functions.lua new file mode 100644 index 0000000..15fa497 --- /dev/null +++ b/client/functions.lua @@ -0,0 +1,62 @@ + +---@type table +local GroupBlips = {} + +---Removes a blip by name +---@param name string +local function removeBlipByName(name) + for i = 1, #GroupBlips do + local blip = GroupBlips[i] + + if blip?.name == name then + SetBlipRoute(blip.blip, false) + RemoveBlip(blip.blip) + table.remove(GroupBlips, i) + break + end + end +end + + +RegisterNetEvent('groups:removeBlip', removeBlipByName) + +---Creates a blip from the data passed +---@param name string +---@param data BlipData +RegisterNetEvent('groups:createBlip', function(name, data) + if not data then return + print('Invalid Data was passed to the create blip event') + end + + -- We remove the blip if it already exists with the same name + removeBlipByName(name) + + local blip + if data.entity then + blip = AddBlipForEntity(data.entity) + elseif data.netId then + blip = AddBlipForEntity(NetworkGetEntityFromNetworkId(data.netId)) + elseif data.radius then + blip = AddBlipForRadius(data.coords.x, data.coords.y, data.coords.z, data.radius) + else + blip = AddBlipForCoord(data.coords.x, data.coords.y, data.coords.z) + end + + if not data.radius then + SetBlipSprite(blip, data.sprite or 1) + SetBlipScale(blip, data.scale or 0.7) + BeginTextCommandSetBlipName('STRING') + AddTextComponentSubstringPlayerName(data.label or 'NO LABEL FOUND') + EndTextCommandSetBlipName(blip) + end + + SetBlipColour(blip, data.color or 1) + SetBlipAlpha(blip, data.alpha or 255) + + if data.route then + SetBlipRoute(blip, true) + SetBlipRouteColour(blip, data.routeColor or 1) + end + + GroupBlips[#GroupBlips + 1] = { name = name, blip = blip } +end) \ No newline at end of file diff --git a/client/main.lua b/client/main.lua index e910ae3..a11af03 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,20 +1,19 @@ ----@diagnostic disable: lowercase-global - local identifier = 'slrn_groups' -local groupStage -- Stores group stage -local finishedNotify -- store answer to leave group --- Bridge -if GetResourceState('qbx_core') == 'started' then - function getPlayerData() - return QBX.PlayerData - end -else - local QBCore = exports['qb-core']:GetCoreObject() +local function sendCustomAppMessage(action, data) + exports['lb-phone']:SendCustomAppMessage( + identifier, { + action = action, + data = data + }) +end - function getPlayerData() - return QBCore.Functions.GetPlayerData() - end +local function sendNotification(message, title) + exports['lb-phone']:SendNotification({ + app = identifier, + title = title or nil, + content = message, + }) end CreateThread(function() @@ -29,8 +28,22 @@ CreateThread(function() description = 'Group app to do stuff together', developer = 'solareon', defaultApp = true, - ui = 'slrn_groups/ui/index.html', - icon = 'https://cfx-nui-slrn_groups/ui/assets/icon.png' + ui = GetCurrentResourceName() .. "/ui/dist/index.html", + -- ui = "http://localhost:3000", -- for local ui build testing + icon = "https://cfx-nui-" .. GetCurrentResourceName() .. "/ui/dist/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/dist/screenshot-light.png", + "https://cfx-nui-" .. GetCurrentResourceName() .. "/ui/dist/screenshot-dark.png" + }, }) if not added then print('Could not add app:', errorMessage) @@ -46,177 +59,67 @@ CreateThread(function() end) end) -local inJob = false -local GroupBlips = {} - -local function FindBlipByName(name) - for i = 1, #GroupBlips do - if GroupBlips[i] and GroupBlips[i].name == name then - return i - end - end -end - -RegisterNetEvent('groups:removeBlip', function(name) - local i = FindBlipByName(name) - if i then - local blip = GroupBlips[i]['blip'] - SetBlipRoute(blip, false) - RemoveBlip(blip) - GroupBlips[i] = nil - end +RegisterNuiCallback('getPlayerData', function(_, cb) + local playerData = { + source = cache.serverId, + } + cb(playerData) end) -local function PhoneNotification(title, description, accept, deny) - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'PhoneNotification', - PhoneNotify = { - title = title, - text = description, - accept = accept, - deny = deny, - }, - }) - finishedNotify = nil - local timeout = 500 - while not finishedNotify and timeout > 0 do - Wait(100) - timeout -= 1 - end - if not finishedNotify then return false end - return finishedNotify == 'success' or false -end - -RegisterNetEvent('groups:createBlip', function(name, data) - if not data then return print('Invalid Data was passed to the create blip event') end - - if FindBlipByName(name) then - TriggerEvent('groups:removeBlip', name) - end - - local blip - if data.entity then - blip = AddBlipForEntity(data.entity) - elseif data.netId then - blip = AddBlipForEntity(NetworkGetEntityFromNetworkId(data.netId)) - elseif data.radius then - blip = AddBlipForRadius(data.coords.x, data.coords.y, data.coords.z, data.radius) - else - blip = AddBlipForCoord(data.coords.x, data.coords.y, data.coords.z) - end - - if not data.color then data.color = 1 end - if not data.alpha then data.alpha = 255 end - - if not data.radius then - if not data.sprite then data.sprite = 1 end - if not data.scale then data.scale = 0.7 end - if not data.label then data.label = 'NO LABEL FOUND' end - - SetBlipSprite(blip, data.sprite) - SetBlipScale(blip, data.scale) - BeginTextCommandSetBlipName('STRING') - AddTextComponentSubstringPlayerName(data.label) - EndTextCommandSetBlipName(blip) - end - - SetBlipColour(blip, data.color) - SetBlipAlpha(blip, data.alpha) - - if data.route then - SetBlipRoute(blip, true) - SetBlipRouteColour(blip, data.routeColor) - end - GroupBlips[#GroupBlips + 1] = { name = name, blip = blip } -end) - -RegisterNUICallback('GetGroupsApp', function(_, cb) - lib.callback('slrn_groups:server:getAllGroups', false, function(getGroups) - cb(getGroups) - end) -end) - -RegisterNetEvent('slrn_groups:client:RefreshGroupsApp', function(Groups, finish) - if finish then inJob = false end - if inJob then return end - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'refreshApp', - data = Groups, - }) +RegisterNuiCallback('getGroupData', function(_, cb) + --noop end) -RegisterNetEvent('slrn_groups:client:AddGroupStage', function(status, stage) - inJob = true - groupStage = stage - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'addGroupStage', - status = stage - }) +RegisterNuiCallback('getGroupJobSteps', function(_, cb) + local groupStages = lib.callback.await('slrn_groups:server:getGroupJobSteps') + cb(groupStages) end) -RegisterNUICallback('jobcenter_CreateJobGroup', function(data, cb) - TriggerServerEvent('slrn_groups:server:jobcenter_CreateJobGroup', data) - cb('ok') +RegisterNuiCallback('createGroup', function(data, cb) + TriggerServerEvent('slrn_groups:server:createGroup', data) + cb({}) end) -RegisterNUICallback('AnsweredNotify', function(data, cb) - finishedNotify = data.type - cb('ok') +RegisterNuiCallback('joinGroup', function(data, cb) + local message = lib.callback.await('slrn_groups:server:joinGroup', false, data) + sendNotification(message) + cb({}) end) -RegisterNUICallback('onStartup', function(data, cb) - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'LoadPhoneData', - PlayerData = getPlayerData(), - }) - if not inJob then - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'LoadJobCenterApp', - }) - else - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'addGroupStage', - status = groupStage - }) - end - cb('ok') +RegisterNuiCallback('leaveGroup', function(_, cb) + local message = lib.callback.await('slrn_groups:server:leaveGroup') + sendNotification(message) + cb({}) end) -RegisterNUICallback('jobcenter_JoinTheGroup', function(data, cb) - TriggerServerEvent('slrn_groups:server:jobcenter_JoinTheGroup', data) - cb('ok') +RegisterNuiCallback('deleteGroup', function(_, cb) + local message = lib.callback.await('slrn_groups:server:deleteGroup') + sendNotification(message) + cb({}) end) -RegisterNetEvent('slrn_groups:client:CustomNotification', function(header, msg) - exports['lb-phone']:SendNotification({ - app = identifier, - title = header, - content = msg, - }) +RegisterNUICallback('getMemberList', function(_, cb) + local groupNames = lib.callback.await('slrn_groups:server:getGroupMembersNames') + cb(groupNames) end) -RegisterNUICallback('jobcenter_leave_grouped', function(data, cb) - if not data then return end - local success = PhoneNotification('Job Center', 'Are you sure you want to leave the group?', 'Accept', 'Deny') - if success then - TriggerServerEvent('slrn_groups:server:jobcenter_leave_grouped', data) - end - cb('ok') +RegisterNUICallback('removeGroupMember', function (data, cb) + local message = lib.callback.await('slrn_groups:server:removeGroupMember', false, data) + sendNotification(message) + cb({}) end) -RegisterNUICallback('jobcenter_DeleteGroup', function(data, cb) - TriggerServerEvent('slrn_groups:server:jobcenter_DeleteGroup', data) - cb('ok') +RegisterNetEvent('slrn_groups:client:refreshGroups', function(groupData) + local currentGroupData, inGroup = lib.callback.await('slrn_groups:server:getGroupMembersNames', false) + sendCustomAppMessage('setCurrentGroup', currentGroupData or {}) + sendCustomAppMessage('setInGroup', inGroup or false) + sendCustomAppMessage('setGroups', groupData) end) -RegisterNUICallback('jobcenter_CheckPlayerNames', function(data, cb) - lib.callback('slrn_groups:server:jobcenter_CheckPlayerNames', false, function(HasName) - cb(HasName) - end, data.id) +RegisterNetEvent('slrn_groups:client:updateGroupStage', function(_, stage) + sendCustomAppMessage('setGroupJobSteps', stage) end) -RegisterCommand('testpass', function() - exports['lb-phone']:SendCustomAppMessage(identifier, { - action = 'testPassword' - }) -end, false) +RegisterNetEvent('slrn_groups:client:CustomNotification', function(header, msg) + sendNotification(msg, header) +end) \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua index 8aa87d5..b71faf3 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -11,16 +11,22 @@ 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/**/*' + "ui/dist/**/*", + "ui/public/**" } -ui_page 'ui/index.html' +ui_page "ui/dist/index.html" dependency '/assetpacks' diff --git a/server/api.lua b/server/api.lua new file mode 100644 index 0000000..c9b0d80 --- /dev/null +++ b/server/api.lua @@ -0,0 +1,439 @@ +local group_class = require 'server.groups' +local utils = require 'server.utils' + +---@type table +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 msg string +---@param type string +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) + end + + for i = 1, #group.members do + 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 header string +---@param msg string +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) + end + + group:triggerGroupEvent('slrn_groups:client:CustomNotification', header or 'NO HEADER', msg or 'NO MSG') +end +utils.exportHandler('pNotifyGroup', api.pNotifyGroup) + +---Triggers a client event for each member of a group (stolen from ox_lib) +---@param eventName string +---@param groupId number +---@param ... any +function api.triggerGroupEvent(eventName, groupId, ...) + local group = api.findGroupById(groupId) + + if not group then + return lib.print.error('triggerGroupEvent was sent an invalid groupId :'..groupId) + end + + group:triggerGroupEvent(eventName, ...) +end +utils.exportHandler('triggerGroupEvent', api.triggerGroupEvent) + +---@class BlipData +---@field entity number? +---@field netId number? +---@field radius number? +---@field coords vector3? +---@field color number? +---@field alpha number? +---@field sprite number? +---@field scale number? +---@field label string? +---@field route boolean? +---@field routeColor number? + +--- Creates a blip for all members of a group +---@param groupId number +---@param name string +---@param data BlipData +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) + end + + group:triggerGroupEvent('groups:createBlip', name, data) +end +utils.exportHandler('CreateBlipForGroup', api.CreateBlipForGroup) + +--- Removes a blip for all members of a group +---@param groupId number +---@param name string +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) + end + + group:triggerGroupEvent('groups:removeBlip', name) +end +utils.exportHandler('RemoveBlipForGroup', api.RemoveBlipForGroup) + +--- Returns the group ID by member's source +---@param src number +---@return number? +function api.GetGroupByMembers(src) + if src then + for i = 1, #groups do + for j = 1, #groups[i].members do + if groups[i].members[j].playerId == src then + return groups[i].id + end + end + end + end +end +utils.exportHandler('GetGroupByMembers', api.GetGroupByMembers) + +--- Returns the group members of a given group +---@param groupId number +---@return number[]? +function api.getGroupMembers(groupId) + local group = api.findGroupById(groupId) + + if not group then + return lib.print.error('getGroupMembers was sent an invalid groupId :'..groupId) + end + + return group:getGroupMembers() +end +utils.exportHandler('getGroupMembers', api.getGroupMembers) + +--- Returns the number of members in a given group +---@param groupId number +---@return number? +function api.getGroupSize(groupId) + local group = api.findGroupById(groupId) + + if not group then + return lib.print.error('getGroupSize was sent an invalid groupId :'..groupId) + end + + return #group.members +end +utils.exportHandler('getGroupSize', api.getGroupSize) + +--- Returns the leader of a group +---@param groupId number +---@return number? +function api.GetGroupLeader(groupId) + local group = api.findGroupById(groupId) + + if not group then + return lib.print.error('GetGroupLeader was sent an invalid groupId :'..groupId) + end + + return group.leader +end +utils.exportHandler('GetGroupLeader', api.GetGroupLeader) + +--- Destroys a group and removes it from the array +---@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) + 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].playerId + + if source ~= group.leader then + utils.notify(group.members[i].playerId, 'The group has been disbanded', 'error') + end + end + end + + 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()) + lib.triggerClientEvent('slrn_groups:client:refreshGroups', -1, api.GetAllGroups()) +end +utils.exportHandler('DestroyGroup', api.DestroyGroup) + +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) + end + + group:addMember(source) + + lib.triggerClientEvent('slrn_groups:client:refreshGroups', -1, api.GetAllGroups()) +end +utils.exportHandler('AddMember', api.AddMember) + +---Checks if the password a user entered is the same as the group password +---@param groupId number +---@param password string +---@return boolean? +function api.isPasswordCorrect(groupId, password) + local group = groups?[groupId] + + if not group then + return lib.print.error('isPasswordCorrect was sent an invalid groupId :'..groupId) + end + + return group:getPassword() == password +end + + + +--- Checks if the player is the leader in the group +---@param src number +---@param groupId number +---@return boolean? +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) + end + + return group.leader == src +end +utils.exportHandler('isGroupLeader', api.isGroupLeader) + + +---Removes a player from a group +---@param source number +---@param groupId number +---@return boolean? +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) + end + + + local memberCount = #group.members + for i = 1, memberCount do + local member = group.members[i] + + 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) + end + + return true + end + end + + return false +end + +--- Sets the group status and stages +---@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 = api.findGroupById(groupId) + + if not group then + return lib.print.error('setJobStatus was sent an invalid groupId :'..groupId) + end + + group.status = status + group.stage = stages + + group:triggerGroupEvent('slrn_groups:client:updateGroupStage', status, stages) +end +utils.exportHandler('setJobStatus', api.setJobStatus) + +--- Returns the group status +---@param groupId number +---@return 'WAITING' | 'IN_PROGRESS' | 'DONE' | nil +function api.getJobStatus(groupId) + local group = api.findGroupById(groupId) + + if not group then + return lib.print.error('getJobStatus was sent an invalid groupId :'..groupId) + end + + return group.status +end +utils.exportHandler('getJobStatus', api.getJobStatus) + +--- Resets the group status and stages +---@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) + end + + group.status = 'WAITING' + group.stage = {} + + group:refreshGroupStages() +end +utils.exportHandler('resetJobStatus', api.resetJobStatus) + +--- Returns the group current stages +---@param groupId number +---@return {id: number, name: string, isDone: boolean}[]? +function api.GetGroupStages(groupId) + local group = api.findGroupById(groupId) + + if not group then + return lib.print.error('GetGroupStages was sent an invalid groupId :'..groupId) + end + + return group.stage +end +utils.exportHandler('GetGroupStages', api.GetGroupStages) + +--- Returns whether or not the group is created by a script +---@param groupId number +---@return boolean? +function api.isGroupTemp(groupId) + local group = api.findGroupById(groupId) + + if not group then + return lib.print.error('isGroupTemp was sent an invalid groupId :'..groupId) + end + + return group.ScriptCreated or false +end +utils.exportHandler('isGroupTemp', api.isGroupTemp) + + +---Gets all the groups thats currently running in the server +---@return {id: number, name: string, memberCount: number}[] +function api.GetAllGroups() + local data = {} + + for i = 1, #groups do + data[i] = groups[i]:getClientData() + end + + return data +end +utils.exportHandler('getAllGroups', api.GetAllGroups) + +--- Returns the group member names +---@param groupId number +---@return string[]? +function api.GetGroupMembersNames(groupId) + local group = api.findGroupById(groupId) + + if not group then + return lib.print.error('GetGroupMembersNames was sent an invalid groupId :'..groupId) + end + + local members = {} + + for i = 1, #group.members do + local member = group.members[i] + + members[i] = { + name = member.name, + playerId = member.playerId, + isLeader = member.playerId == group.leader + } + end + + return members +end +utils.exportHandler('GetGroupMembersNames', api.GetGroupMembersNames) + +--- Changes the leader of a group +---@param groupId number +---@return boolean? +function api.ChangeGroupLeader(groupId) + local group = api.findGroupById(groupId) + + if not group then + 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].playerId ~= group.leader then + group.leader = members[i].playerId + return true + end + end + end + + return false +end + +--- Creates a new group +---@param src number +---@param name string +---@param password string? +---@return number +function api.CreateGroup(src, name, password) + groupIndex = groupIndex + 1 + local id = groupIndex + + local group = group_class:new(id, name, password, src, true) + + groups[#groups + 1] = group + + -- Send non-sensitive data to all clients (id, name, memberCount) + lib.triggerClientEvent('slrn_groups:client:refreshGroups', -1, api.GetAllGroups()) + + return id +end +utils.exportHandler('CreateGroup', api.CreateGroup) + +return api diff --git a/server/groups.lua b/server/groups.lua new file mode 100644 index 0000000..2958e50 --- /dev/null +++ b/server/groups.lua @@ -0,0 +1,110 @@ +---@class groups : OxClass +---@field public id number +---@field public name string +---@field public leader number +---@field public ScriptCreated boolean +---@field public status string +---@field public stage {id: number, name: string, isDone: boolean}[] +---@field public members {name: string, playerId: number}[] +---@field private private {password: string} +local groups = lib.class('groups') + + + +---Constructs a new instance of the groups class. +---@param id number +---@param name string +---@param password string? +---@param leader number +---@param ScriptCreated boolean +function groups:constructor(id, name, password, leader, ScriptCreated) + self.id = id + self.name = name + self.private.password = password or lib.string.random('1111111') + self.leader = leader + self.ScriptCreated = ScriptCreated + self.members = { + { + name = GetPlayerName(leader), + playerId = leader + } + } + self.stage = {} + self.status = 'WAITING' +end + + +---Adds a member to the group. +---@param source number +function groups:addMember(source) + self.members[#self.members+1] = { + name = GetPlayerName(source), + playerId = source + } +end + + +---Gets the non-sensitive data of the group. +---@return {id: number, name: string, memberCount: number} +function groups:getClientData() + return { + id = self.id, + name = self.name, + memberCount = #self.members + } +end + +---Refreshes the group stages for all members. +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].playerId + + TriggerClientEvent('slrn_groups:client:updateGroupStage', source, self.status, self.stage) + TriggerClientEvent('slrn_groups:client:refreshGroups', source, self:getClientData(), true) + end + end +end + + +---Gets the group member sources. +---@return number[] +function groups:getGroupMembers() + local members = {} + + for i = 1, #self.members do + members[i] = self.members[i].playerId + end + + return members +end + +---Gets the password of a group +---@return string +function groups:getPassword() + return self.private.password +end + + + +---Triggers an event for all members of the group. (Stolen from ox_lib so thanks to the original author) +---@param eventName string +---@param ... any +function groups:triggerGroupEvent(eventName, ...) + local payload = msgpack.pack_args(...) + local payloadLen = #payload + + for i = 1, #self.members do + TriggerClientEventInternal(eventName, self.members[i].playerId --[[@as string]], payload, payloadLen) + end +end + + + + + + + + +return groups \ No newline at end of file diff --git a/server/main.lua b/server/main.lua index 30b3e37..6fb42f6 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,4 +1,3 @@ ----@diagnostic disable: lowercase-global if not lib then return end lib.versionCheck('solareon/slrn_groups') @@ -8,355 +7,154 @@ if GetCurrentResourceName() ~= 'slrn_groups' then return end -local playerData = {} -local playerGroup = {} +local api = require 'server.api' -local function exportHandler(exportName, func) - AddEventHandler(('__cfx_export_qb-phone_%s'):format(exportName), function(setCB) - setCB(func) - end) - exports(exportName, func) -- support modern exports -end - --- Bridge -if GetResourceState('qbx_core') == 'started' then +lib.callback.register('slrn_groups:server:removeGroupMember', function(source, targetId) + local groupId = api.GetGroupByMembers(source) - function getPlayer(id) - return exports.qbx_core:GetPlayer(id) + if groupId then + if api.isGroupLeader(source, groupId) then + if api.RemovePlayerFromGroup(targetId, groupId) then + return 'Removed '..GetPlayerName(targetId).. ' from the group' + end + end end - function doNotification(src, text, nType) - exports.qbx_core:Notify(src, text, nType) - end -else -- Switch to QBox and save your sanity - local QBCore = exports['qb-core']:GetCoreObject() + return 'Error removing player' +end) - function getPlayer(id) - return QBCore.Functions.GetPlayer(id) - end +lib.callback.register('slrn_groups:server:promoteGroupMember', function(source, targetId) + local groupId = api.GetGroupByMembers(source) - function doNotification(src, text, nType) - TriggerClientEvent('QBCore:Notify', src, text, nType) + if groupId then + if api.isGroupLeader(source, groupId) then + if api.ChangeGroupLeader(groupId, targetId) then + return 'Promoted '..GetPlayerName(targetId).. ' to Leader' + end + end end -end --- Utility functions -local function GetPlayerCharName(src) - local player = getPlayer(src) - return player.PlayerData.charinfo.firstname..' '..player.PlayerData.charinfo.lastname -end + return 'Error promoting player' +end) -local function NotifyGroup(group, msg, type) - if not group or not playerGroup[group] then return lib.print.error('Group not found...') end - for _, v in pairs(playerGroup[group].members) do - doNotification(v.Player, msg, type) - end -end -exportHandler('NotifyGroup', NotifyGroup) - -local function pNotifyGroup(group, header, msg) - if not group or not playerGroup[group] then return lib.print.error('Group not found...') end - for _, v in pairs(playerGroup[group].members) do - TriggerClientEvent('slrn_groups:client:CustomNotification', v.Player, - header or 'NO HEADER', - msg or 'NO MSG' - ) - end -end -exportHandler('pNotifyGroup', pNotifyGroup) +lib.callback.register('slrn_groups:server:deleteGroup', function(source) + local groupId = api.GetGroupByMembers(source) -local function CreateBlipForGroup(groupID, name, data) - if not groupID then return lib.print.error('CreateBlipForGroup was sent an invalid groupID :'..groupID) end + if groupId then + if api.isGroupLeader(source, groupId) then + api.DestroyGroup(groupId) - for i=1, #playerGroup[groupID].members do - TriggerClientEvent('groups:createBlip', playerGroup[groupID].members[i].Player, name, data) + return 'Group Deleted' + else + if api.RemovePlayerFromGroup(source, groupId) then + return 'Left Group' + end + end end -end -exportHandler('CreateBlipForGroup', CreateBlipForGroup) - -local function RemoveBlipForGroup(groupID, name) - if not groupID then return lib.print.error('CreateBlipForGroup was sent an invalid groupID :'..groupID) end - for i=1, #playerGroup[groupID].members do - TriggerClientEvent('groups:removeBlip', playerGroup[groupID].members[i].Player, name) - end -end -exportHandler('RemoveBlipForGroup', RemoveBlipForGroup) + return 'Error leaving group' +end) +---Joins a specific group if the password is correct +---@param source number +---@param data {id: number, pass: string} +lib.callback.register('slrn_groups:server:joinGroup', function(source, data) + if api.isPasswordCorrect(data.id, data.pass) then --- All group functions to get members leaders and size. -local function GetGroupByMembers(src) - if not playerData[src] then return nil end - for group, _ in pairs(playerGroup) do - for _, v in pairs (playerGroup[group].members) do - if v.Player == src then - return group - end - end - end -end -exportHandler('GetGroupByMembers', GetGroupByMembers) - -local function getGroupMembers(groupID) - if not groupID then return lib.print.error('getGroupMembers was sent an invalid groupID :'..groupID) end - local temp = {} - for _,v in pairs(playerGroup[groupID].members) do - temp[#temp+1] = v.Player - end - return temp -end -exportHandler('getGroupMembers', getGroupMembers) - -local function getGroupSize(groupID) - if not groupID then return lib.print.error('getGroupSize was sent an invalid groupID :'..groupID) end - if not playerGroup[groupID] then return lib.print.error('getGroupSize was sent an invalid groupID :'..groupID) end - return #playerGroup[groupID].members -end -exportHandler('getGroupSize', getGroupSize) - -local function GetGroupLeader(groupID) - if not groupID then return lib.print.error('GetGroupLeader was sent an invalid groupID :'..groupID) end - return playerGroup[groupID].leader -end -exportHandler('GetGroupLeader', GetGroupLeader) - -local function DestroyGroup(groupID) - if not playerGroup[groupID] then return lib.print.error('DestroyGroup was sent an invalid groupID :'..groupID) end - local members = getGroupMembers(groupID) - if members and #members > 0 then - for i = 1, #members do - if members[i] then - playerData[members[i]] = false - end - end - end + api.pNotifyGroup(data.id, 'Groups', GetPlayerName(source)..' has joined the group!') - exports['qb-phone']:resetJobStatus(groupID) - TriggerEvent('slrn_groups:server:GroupDeleted', groupID, members) - - playerGroup[groupID] = nil - TriggerClientEvent('slrn_groups:client:RefreshGroupsApp', -1, playerGroup) - -end -exportHandler('DestroyGroup', DestroyGroup) - -local function RemovePlayerFromGroup(src, groupID, disconnected) - if not playerData[src] or not playerGroup[groupID] then return lib.print.error('RemovePlayerFromGroup was sent an invalid groupID :'..groupID) end - local g = playerGroup[groupID].members - for k,v in pairs(g) do - if v.Player == src then - table.remove(playerGroup[groupID].members, k) - playerGroup[groupID].Users -= 1 - playerData[src] = false - pNotifyGroup(groupID, 'Job Center', v.name..' Has left the group') - TriggerClientEvent('slrn_groups:client:RefreshGroupsApp', -1, playerGroup) - if not disconnected then doNotification(src, 'You have left the group', 'primary') end - - if playerGroup[groupID].Users <= 0 then - DestroyGroup(groupID) - end + api.AddMember(data.id, source) - return - end + return 'You joined the group' + else + return 'Invalid Password' end -end +end) -local function ChangeGroupLeader(groupID) - local m = playerGroup[groupID].members - local l = GetGroupLeader(groupID) - if #m > 1 then - for i=1, #m do - if m[i].Player ~= l then - playerGroup[groupID].leader = m[i].Player - return true - end - end - end - return false -end +lib.callback.register('slrn_groups:server:leaveGroup', function(source) + local groupId = api.GetGroupByMembers(source) -local function isGroupLeader(src, groupID) - if not groupID then return end - local grouplead = GetGroupLeader(groupID) - return grouplead == src or false -end -exportHandler('isGroupLeader', isGroupLeader) - ----- All the job functions for the groups - -local function setJobStatus(groupID, status, stages) - if not groupID then return lib.print.error('setJobStatus was sent an invalid groupID :'..groupID) end - playerGroup[groupID].status = status - playerGroup[groupID].stage = stages - local m = getGroupMembers(groupID) - if not m then return end - for i=1, #m do - if m[i] then - TriggerClientEvent('slrn_groups:client:AddGroupStage', m[i], status, stages) - end - end -end -exportHandler('setJobStatus', setJobStatus) - -local function getJobStatus(groupID) - if not groupID then return lib.print.error('getJobStatus was sent an invalid groupID :'..groupID) end - return playerGroup[groupID].status -end -exportHandler('getJobStatus', getJobStatus) - -local function resetJobStatus(groupID) - if not groupID then return lib.print.error('setJobStatus was sent an invalid groupID :'..groupID) end - playerGroup[groupID].status = 'WAITING' - playerGroup[groupID].stage = {} - local m = getGroupMembers(groupID) - if not m then return end - for i=1, #m do - if m[i] then - TriggerClientEvent('slrn_groups:client:AddGroupStage', m[i], playerGroup[groupID].status, playerGroup[groupID].stage) - TriggerClientEvent('slrn_groups:client:RefreshGroupsApp', m[i], playerGroup, true) - end - end -end -exportHandler('resetJobStatus', resetJobStatus) + if groupId then + api.RemovePlayerFromGroup(source, groupId) -AddEventHandler('playerDropped', function() - local src = source - local groupID = GetGroupByMembers(src) - if groupID then - if isGroupLeader(src, groupID) then - if ChangeGroupLeader(groupID) then - RemovePlayerFromGroup(src, groupID, true) - else - DestroyGroup(groupID) - end - else - RemovePlayerFromGroup(src, groupID, true) - end + return 'Left Group' end end) -RegisterNetEvent('slrn_groups:server:jobcenter_CreateJobGroup', function(data) - local src = source - assert(src ~= nil, 'invalid source') - - local player = getPlayer(src) - if playerData[src] then doNotification(src, 'You have already created a group', 'error') return end - if not data or not data.pass or not data.name then return end - playerData[src] = true - local ID = #playerGroup+1 - playerGroup[ID] = { - id = ID, - status = 'WAITING', - GName = data.name, - GPass = data.pass, - Users = 1, - leader = src, - members = { - {name = GetPlayerCharName(src), CID = player.PlayerData.citizenid, Player = src} - }, - stage = {}, - } - TriggerClientEvent('slrn_groups:client:RefreshGroupsApp', -1, playerGroup) +---Creates a new group +---@param data {name: string, pass: string} +RegisterNetEvent('slrn_groups:server:createGroup', function(data) + api.CreateGroup(source, data.name, data.pass) end) -RegisterNetEvent('TestGroups', function() - local src = source - local TestTable = { - {name = 'Pick Up Truck', isDone = true, id = 1}, - {name = 'Pick up garbage', isDone = false , id = 2}, - {name = 'Drop off garbage', isDone = false , id = 3}, - } - setJobStatus((GetGroupByMembers(src)), 'garbage', TestTable) -end) +---Gets all the player names of the players in the group +---@param source number +---@return table? +lib.callback.register('slrn_groups:server:getGroupMembers', function(source) + local groupId = api.GetGroupByMembers(source) -RegisterNetEvent('slrn_groups:server:jobcenter_DeleteGroup', function(data) - local src = source - if not playerData[src] then return lib.print.error('You are not in a group?!?') end - if GetGroupLeader(data.delete) == src then - DestroyGroup(data.delete) - else - RemovePlayerFromGroup(src, data.delete) + if groupId then + return api.getGroupMembers(groupId) end end) -lib.callback.register('slrn_groups:server:GetGroupsApp', function(_) - return playerGroup -end) +---Gets all the player names of the players in the group +---@param source number +---@return table? +---@return number | boolean? +lib.callback.register('slrn_groups:server:getGroupMembersNames', function(source) + local groupId = api.GetGroupByMembers(source) -RegisterNetEvent('slrn_groups:server:jobcenter_JoinTheGroup', function(data) - local src = source - assert(src ~= nil, 'invalid source') + if groupId then + return api.GetGroupMembersNames(groupId), groupId or false + end +end) - local player = getPlayer(src) - if playerData[src] then return doNotification(src, 'You are already a part of a group!', 'success') end - local name = GetPlayerCharName(src) - pNotifyGroup(data.id, 'Job Center', name..' Has joined the group') - playerGroup[data.id].members[#playerGroup[data.id].members+1] = {name = name, CID = player.PlayerData.citizenid, Player = src} - playerGroup[data.id].Users += 1 - playerData[src] = true - doNotification(src, 'You joined the group', 'success') - TriggerClientEvent('slrn_groups:client:RefreshGroupsApp', -1, playerGroup) +---Get app startup data +---@param source number +---@return table +lib.callback.register('slrn_groups:server:getSetupAppData', function(source) + local groupId = api.GetGroupByMembers(source) + 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) -local function GetGroupStages(groupID) - if not groupID then return lib.print.error('GetGroupStages was sent an invalid groupID :'..groupID) end - return playerGroup[groupID].stage -end -exportHandler('GetGroupStages', GetGroupStages) +---Get group stages +---@param source number +---@return table? +lib.callback.register('slrn_groups:server:getGroupJobSteps', function(source) + local groupId = api.GetGroupByMembers(source) -lib.callback.register('slrn_groups:server:getAllGroups', function(source) - if playerData[source] then - return playerGroup, true, getJobStatus(GetGroupByMembers(source)), GetGroupStages(GetGroupByMembers(source)) - else - return playerGroup, false + if groupId then + return api.GetGroupStages(groupId) end end) -lib.callback.register('slrn_groups:server:jobcenter_CheckPlayerNames', function(_, csn) - local Names = {} - for _, v in pairs(playerGroup[csn].members) do - Names[#Names+1] = v.name - end - return Names -end) +AddEventHandler('playerDropped', function() + local groupId = api.GetGroupByMembers(source) -RegisterNetEvent('slrn_groups:server:jobcenter_leave_grouped', function(data) - local src = source - if not playerData[src] then return end - RemovePlayerFromGroup(src, data.id) + if groupId then + if api.isGroupLeader(source, groupId) then + if api.ChangeGroupLeader(groupId) then + api.RemovePlayerFromGroup(source, groupId) + else + api.DestroyGroup(groupId) + end + else + api.RemovePlayerFromGroup(source, groupId) + end + end end) - -local function isGroupTemp(groupID) - if not groupID or not playerGroup[groupID] then return lib.print.error('isGroupTemp was sent an invalid groupID :'..groupID) end - return playerGroup[groupID].ScriptCreated or false -end -exportHandler('isGroupTemp', isGroupTemp) - - -local function CreateGroup(src, name, password) - if not src or not name then return end - local Player = getPlayer(src) - playerData[src] = true - local id = #playerGroup+1 - playerGroup[id] = { - id = id, - status = 'WAITING', - GName = name, - GPass = password or lib.string.random('1111111'), - Users = 1, - leader = src, - members = { - {name = GetPlayerCharName(src), CID = Player.PlayerData.citizenid, Player = src} - }, - stage = {}, - ScriptCreated = true, - } - - TriggerClientEvent('slrn_groups:client:RefreshGroupsApp', -1, playerGroup) - return id -end -exportHandler('CreateGroup', CreateGroup) diff --git a/server/utils.lua b/server/utils.lua new file mode 100644 index 0000000..5e189f5 --- /dev/null +++ b/server/utils.lua @@ -0,0 +1,36 @@ +local utils = {} + + + + + +---Notifies a player using ox_lib +---@param src number +---@param text string +---@param type string +function utils.notify(src, text, type) + lib.notify(src, { + description = text, + type = type, + duration = 5000 + }) +end + + +---Create backwards compatability for qb-phone exports +---@param exportName string +---@param func function +function utils.exportHandler(exportName, func) + AddEventHandler(('__cfx_export_qb-phone_%s'):format(exportName), function(setCB) + setCB(func) + end) + + exports(exportName, func) -- support modern exports +end + + + + + + +return utils \ No newline at end of file diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 0000000..9e2e4d1 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,22 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +dist \ No newline at end of file diff --git a/ui/assets/icon.png b/ui/assets/icon.png deleted file mode 100644 index b29d093..0000000 Binary files a/ui/assets/icon.png and /dev/null differ diff --git a/ui/colors.css b/ui/colors.css deleted file mode 100644 index bcfb5e0..0000000 --- a/ui/colors.css +++ /dev/null @@ -1,15 +0,0 @@ -:root { - --background-primary: #f5f5f5; - --background-highlight: rgb(220, 220, 220); - - --text-primary: #000000; - --text-secondary: #8e8e93; -} - -[data-theme='dark'] { - --background-primary: #000000; - --background-highlight: rgb(20, 20, 20); - - --text-primary: #f2f2f7; - --text-secondary: #6f6f6f; -} diff --git a/ui/dev.js b/ui/dev.js deleted file mode 100644 index 613ea96..0000000 --- a/ui/dev.js +++ /dev/null @@ -1,19 +0,0 @@ -// You can ignore this file. All it does is make the UI work on your browser. - -window.addEventListener("load", () => { - if (window.invokeNative) { - const phoneWrapper = document.getElementById("phone-wrapper"); - const app = phoneWrapper.querySelector(".app"); - - phoneWrapper.parentNode.insertBefore(app, phoneWrapper); - phoneWrapper.parentNode.removeChild(phoneWrapper); - return; - } - - document.getElementById("phone-wrapper").style.display = "block"; - document.body.style.visibility = "visible"; - - const center = () => document.getElementById("phone-wrapper").style.scale = window.innerWidth / 1920; - center(); - window.addEventListener("resize", center); -}); diff --git a/ui/index.html b/ui/index.html index 0b0c48d..97f6cf0 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1,76 +1,19 @@ - + - Groups - - + + + + SLRN Groups - - - - - - - -
-
-
-
-
- Group Name - Group Password - Confirm Password -

-
Cancel
-
Create
-
-
- -
-
- Enter Password -

-
Cancel
-
Join
-
-
- -
-
-
Members
-
-
RETURN
-
-
-
-
Join a group or browse groups currently busy
-
-
-
CREATE GROUP
-
-
-
-
-
-
-
-
- - - + +
+ diff --git a/ui/jquery.timeago.js b/ui/jquery.timeago.js deleted file mode 100644 index ec80c32..0000000 --- a/ui/jquery.timeago.js +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Timeago is a jQuery plugin that makes it easy to support automatically - * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). - * - * @name timeago - * @version 1.6.7 - * @requires jQuery >=1.5.0 <4.0 - * @author Ryan McGeary - * @license MIT License - http://www.opensource.org/licenses/mit-license.php - * - * For usage and examples, visit: - * http://timeago.yarp.com/ - * - * Copyright (c) 2008-2019, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org) - */ - -(function (factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['jquery'], factory); - } else if (typeof module === 'object' && typeof module.exports === 'object') { - factory(require('jquery')); - } else { - // Browser globals - factory(jQuery); - } -}(function ($) { - $.timeago = function(timestamp) { - if (timestamp instanceof Date) { - return inWords(timestamp); - } else if (typeof timestamp === "string") { - return inWords($.timeago.parse(timestamp)); - } else if (typeof timestamp === "number") { - return inWords(new Date(timestamp)); - } else { - return inWords($.timeago.datetime(timestamp)); - } - }; - var $t = $.timeago; - - $.extend($.timeago, { - settings: { - refreshMillis: 60000, - allowPast: true, - allowFuture: false, - localeTitle: false, - cutoff: 0, - autoDispose: true, - strings: { - prefixAgo: null, - prefixFromNow: null, - suffixAgo: "ago", - suffixFromNow: "from now", - inPast: "any moment now", - seconds: "less than a minute", - minute: "about a minute", - minutes: "%d minutes", - hour: "about an hour", - hours: "about %d hours", - day: "a day", - days: "%d days", - month: "about a month", - months: "%d months", - year: "about a year", - years: "%d years", - wordSeparator: " ", - numbers: [] - } - }, - - inWords: function(distanceMillis) { - if (!this.settings.allowPast && ! this.settings.allowFuture) { - throw 'timeago allowPast and allowFuture settings can not both be set to false.'; - } - - var $l = this.settings.strings; - var prefix = $l.prefixAgo; - var suffix = $l.suffixAgo; - if (this.settings.allowFuture) { - if (distanceMillis < 0) { - prefix = $l.prefixFromNow; - suffix = $l.suffixFromNow; - } - } - - if (!this.settings.allowPast && distanceMillis >= 0) { - return this.settings.strings.inPast; - } - - var seconds = Math.abs(distanceMillis) / 1000; - var minutes = seconds / 60; - var hours = minutes / 60; - var days = hours / 24; - var years = days / 365; - - function substitute(stringOrFunction, number) { - var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction; - var value = ($l.numbers && $l.numbers[number]) || number; - return string.replace(/%d/i, value); - } - - var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || - seconds < 90 && substitute($l.minute, 1) || - minutes < 45 && substitute($l.minutes, Math.round(minutes)) || - minutes < 90 && substitute($l.hour, 1) || - hours < 24 && substitute($l.hours, Math.round(hours)) || - hours < 42 && substitute($l.day, 1) || - days < 30 && substitute($l.days, Math.round(days)) || - days < 45 && substitute($l.month, 1) || - days < 365 && substitute($l.months, Math.round(days / 30)) || - years < 1.5 && substitute($l.year, 1) || - substitute($l.years, Math.round(years)); - - var separator = $l.wordSeparator || ""; - if ($l.wordSeparator === undefined) { separator = " "; } - return $.trim([prefix, words, suffix].join(separator)); - }, - - parse: function(iso8601) { - var s = $.trim(iso8601); - s = s.replace(/\.\d+/,""); // remove milliseconds - s = s.replace(/-/,"/").replace(/-/,"/"); - s = s.replace(/T/," ").replace(/Z/," UTC"); - s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 - s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900 - return new Date(s); - }, - datetime: function(elem) { - var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); - return $t.parse(iso8601); - }, - isTime: function(elem) { - // jQuery's `is()` doesn't play well with HTML5 in IE - return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); - } - }); - - // functions that can be called via $(el).timeago('action') - // init is default when no action is given - // functions are called with context of a single element - var functions = { - init: function() { - functions.dispose.call(this); - var refresh_el = $.proxy(refresh, this); - refresh_el(); - var $s = $t.settings; - if ($s.refreshMillis > 0) { - this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis); - } - }, - update: function(timestamp) { - var date = (timestamp instanceof Date) ? timestamp : $t.parse(timestamp); - $(this).data('timeago', { datetime: date }); - if ($t.settings.localeTitle) { - $(this).attr("title", date.toLocaleString()); - } - refresh.apply(this); - }, - updateFromDOM: function() { - $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) }); - refresh.apply(this); - }, - dispose: function () { - if (this._timeagoInterval) { - window.clearInterval(this._timeagoInterval); - this._timeagoInterval = null; - } - } - }; - - $.fn.timeago = function(action, options) { - var fn = action ? functions[action] : functions.init; - if (!fn) { - throw new Error("Unknown function name '"+ action +"' for timeago"); - } - // each over objects here and call the requested function - this.each(function() { - fn.call(this, options); - }); - return this; - }; - - function refresh() { - var $s = $t.settings; - - //check if it's still visible - if ($s.autoDispose && !$.contains(document.documentElement,this)) { - //stop if it has been removed - $(this).timeago("dispose"); - return this; - } - - var data = prepareData(this); - - if (!isNaN(data.datetime)) { - if ( $s.cutoff === 0 || Math.abs(distance(data.datetime)) < $s.cutoff) { - $(this).text(inWords(data.datetime)); - } else { - if ($(this).attr('title').length > 0) { - $(this).text($(this).attr('title')); - } - } - } - return this; - } - - function prepareData(element) { - element = $(element); - if (!element.data("timeago")) { - element.data("timeago", { datetime: $t.datetime(element) }); - var text = $.trim(element.text()); - if ($t.settings.localeTitle) { - element.attr("title", element.data('timeago').datetime.toLocaleString()); - } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { - element.attr("title", text); - } - } - return element.data("timeago"); - } - - function inWords(date) { - return $t.inWords(distance(date)); - } - - function distance(date) { - return (new Date().getTime() - date.getTime()); - } - - // fix for IE6 suckage - document.createElement("abbr"); - document.createElement("time"); -})); diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..42ec520 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,38 @@ +{ + "name": "slrn_groups", + "version": "2.0.0", + "private": true, + "homepage": "./", + "dependencies": { + "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "@vitejs/plugin-react": "^4.3.1", + "clsx": "^2.1.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tw-colors": "^3.3.1", + "vite": "^5.3.1", + "zustand": "^4.5.2" + }, + "scripts": { + "start": "vite", + "start:game": "vite build --watch", + "dev": "vite dev", + "build": "vite build", + "serve": "vite preview" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "devDependencies": { + "autoprefixer": "^10.4.19", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.4" + } +} diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml new file mode 100644 index 0000000..2a2ce9b --- /dev/null +++ b/ui/pnpm-lock.yaml @@ -0,0 +1,2877 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@fortawesome/free-solid-svg-icons': + specifier: ^6.5.2 + version: 6.5.2 + '@fortawesome/react-fontawesome': + specifier: ^0.2.2 + version: 0.2.2(@fortawesome/fontawesome-svg-core@6.5.2)(react@18.3.1) + '@testing-library/jest-dom': + specifier: ^5.17.0 + version: 5.17.0 + '@testing-library/react': + specifier: ^13.4.0 + version: 13.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@testing-library/user-event': + specifier: ^13.5.0 + version: 13.5.0(@testing-library/dom@8.20.1) + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.3.1(vite@5.3.1(@types/node@20.14.8)) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + tw-colors: + specifier: ^3.3.1 + version: 3.3.1(tailwindcss@3.4.4) + vite: + specifier: ^5.3.1 + version: 5.3.1(@types/node@20.14.8) + zustand: + specifier: ^4.5.2 + version: 4.5.2(@types/react@18.3.3)(react@18.3.1) + devDependencies: + autoprefixer: + specifier: ^10.4.19 + version: 10.4.19(postcss@8.4.38) + postcss: + specifier: ^8.4.38 + version: 8.4.38 + tailwindcss: + specifier: ^3.4.4 + version: 3.4.4 + +packages: + + '@adobe/css-tools@4.4.0': + resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.24.7': + resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.24.7': + resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.24.7': + resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.24.7': + resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-environment-visitor@7.24.7': + resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-function-name@7.24.7': + resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-hoist-variables@7.24.7': + resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.24.7': + resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.24.7': + resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.24.7': + resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-simple-access@7.24.7': + resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-split-export-declaration@7.24.7': + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.24.7': + resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.24.7': + resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.24.7': + resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.24.7': + resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.24.7': + resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.24.7': + resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.24.7': + resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.24.7': + resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.24.7': + resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.24.7': + resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@fortawesome/fontawesome-common-types@6.5.2': + resolution: {integrity: sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==} + engines: {node: '>=6'} + + '@fortawesome/fontawesome-svg-core@6.5.2': + resolution: {integrity: sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==} + engines: {node: '>=6'} + + '@fortawesome/free-solid-svg-icons@6.5.2': + resolution: {integrity: sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==} + engines: {node: '>=6'} + + '@fortawesome/react-fontawesome@0.2.2': + resolution: {integrity: sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==} + peerDependencies: + '@fortawesome/fontawesome-svg-core': ~1 || ~6 + react: '>=16.3' + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/rollup-android-arm-eabi@4.18.0': + resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.18.0': + resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.18.0': + resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.18.0': + resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.18.0': + resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.18.0': + resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.18.0': + resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.18.0': + resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.18.0': + resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.18.0': + resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.18.0': + resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.18.0': + resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.18.0': + resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.18.0': + resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + cpu: [x64] + os: [win32] + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@testing-library/dom@8.20.1': + resolution: {integrity: sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==} + engines: {node: '>=12'} + + '@testing-library/jest-dom@5.17.0': + resolution: {integrity: sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==} + engines: {node: '>=8', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@13.4.0': + resolution: {integrity: sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==} + engines: {node: '>=12'} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + + '@testing-library/user-event@13.5.0': + resolution: {integrity: sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.12': + resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + + '@types/node@20.14.8': + resolution: {integrity: sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==} + + '@types/prop-types@15.7.12': + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + + '@types/react-dom@18.3.0': + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + + '@types/react@18.3.3': + resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/testing-library__jest-dom@5.14.9': + resolution: {integrity: sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.32': + resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + + '@vitejs/plugin-react@4.3.1': + resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + aria-query@5.1.3: + resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + autoprefixer@10.4.19: + resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.23.1: + resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001636: + resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-equal@2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} + engines: {node: '>= 0.4'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.4.811: + resolution: {integrity: sha512-CDyzcJ5XW78SHzsIOdn27z8J4ist8eaFLhdto2hSMSJQgsiwvbv2fbizcKUICryw1Wii1TI/FEkvzvJsR3awrA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + foreground-child@3.2.1: + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + engines: {node: '>=14'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.2: + resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} + engines: {node: '>=16 || 14 >=14.18'} + hasBin: true + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + + is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.14.0: + resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.0: + resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} + engines: {node: '>=14'} + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash.foreach@4.5.0: + resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.0.1: + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.0: + resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.18.0: + resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stop-iteration-iterator@1.0.0: + resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + engines: {node: '>= 0.4'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.4: + resolution: {integrity: sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==} + engines: {node: '>=14.0.0'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tw-colors@3.3.1: + resolution: {integrity: sha512-PH6NShNtDzPCm6zjl0SZe3kmdYSfDS7Sk4mWa9+KzaeSH1ZmpLRrBjZoBJKaFcDB3o7iuFPPg9+HtW05pGPQyQ==} + peerDependencies: + tailwindcss: '>=3.0.0' + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + update-browserslist-db@1.0.16: + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-sync-external-store@1.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@5.3.1: + resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.4.5: + resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} + engines: {node: '>= 14'} + hasBin: true + + zustand@4.5.2: + resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + +snapshots: + + '@adobe/css-tools@4.4.0': {} + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/compat-data@7.24.7': {} + + '@babel/core@7.24.7': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) + '@babel/helpers': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + convert-source-map: 2.0.0 + debug: 4.3.5 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.24.7': + dependencies: + '@babel/types': 7.24.7 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + + '@babel/helper-compilation-targets@7.24.7': + dependencies: + '@babel/compat-data': 7.24.7 + '@babel/helper-validator-option': 7.24.7 + browserslist: 4.23.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-environment-visitor@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-function-name@7.24.7': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/helper-hoist-variables@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-module-imports@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.24.7': {} + + '@babel/helper-simple-access@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-split-export-declaration@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-string-parser@7.24.7': {} + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/helper-validator-option@7.24.7': {} + + '@babel/helpers@7.24.7': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@babel/parser@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/runtime@7.24.7': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/traverse@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + debug: 4.3.5 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.24.7': + dependencies: + '@babel/helper-string-parser': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@fortawesome/fontawesome-common-types@6.5.2': {} + + '@fortawesome/fontawesome-svg-core@6.5.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.5.2 + + '@fortawesome/free-solid-svg-icons@6.5.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.5.2 + + '@fortawesome/react-fontawesome@0.2.2(@fortawesome/fontawesome-svg-core@6.5.2)(react@18.3.1)': + dependencies: + '@fortawesome/fontawesome-svg-core': 6.5.2 + prop-types: 15.8.1 + react: 18.3.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.14.8 + '@types/yargs': 17.0.32 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/rollup-android-arm-eabi@4.18.0': + optional: true + + '@rollup/rollup-android-arm64@4.18.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.18.0': + optional: true + + '@rollup/rollup-darwin-x64@4.18.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.18.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.18.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.18.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.18.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.18.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.18.0': + optional: true + + '@sinclair/typebox@0.27.8': {} + + '@testing-library/dom@8.20.1': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/runtime': 7.24.7 + '@types/aria-query': 5.0.4 + aria-query: 5.1.3 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@5.17.0': + dependencies: + '@adobe/css-tools': 4.4.0 + '@babel/runtime': 7.24.7 + '@types/testing-library__jest-dom': 5.14.9 + aria-query: 5.3.0 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.5.16 + lodash: 4.17.21 + redent: 3.0.0 + + '@testing-library/react@13.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@testing-library/dom': 8.20.1 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@testing-library/user-event@13.5.0(@testing-library/dom@8.20.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@testing-library/dom': 8.20.1 + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.24.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.24.7 + + '@types/estree@1.0.5': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.12': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/node@20.14.8': + dependencies: + undici-types: 5.26.5 + + '@types/prop-types@15.7.12': {} + + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 18.3.3 + + '@types/react@18.3.3': + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + + '@types/stack-utils@2.0.3': {} + + '@types/testing-library__jest-dom@5.14.9': + dependencies: + '@types/jest': 29.5.12 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.32': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@vitejs/plugin-react@4.3.1(vite@5.3.1(@types/node@20.14.8))': + dependencies: + '@babel/core': 7.24.7 + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.3.1(@types/node@20.14.8) + transitivePeerDependencies: + - supports-color + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + aria-query@5.1.3: + dependencies: + deep-equal: 2.2.3 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + autoprefixer@10.4.19(postcss@8.4.38): + dependencies: + browserslist: 4.23.1 + caniuse-lite: 1.0.30001636 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.0.1 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.23.1: + dependencies: + caniuse-lite: 1.0.30001636 + electron-to-chromium: 1.4.811 + node-releases: 2.0.14 + update-browserslist-db: 1.0.16(browserslist@4.23.1) + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001636: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + ci-info@3.9.0: {} + + clsx@2.1.1: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + commander@4.1.1: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css.escape@1.5.1: {} + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + debug@4.3.5: + dependencies: + ms: 2.1.2 + + deep-equal@2.2.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + es-get-iterator: 1.1.3 + get-intrinsic: 1.2.4 + is-arguments: 1.1.1 + is-array-buffer: 3.0.4 + is-date-object: 1.0.5 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + isarray: 2.0.5 + object-is: 1.1.6 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + side-channel: 1.0.6 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.15 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + dequal@2.0.3: {} + + didyoumean@1.2.2: {} + + diff-sequences@29.6.3: {} + + dlv@1.1.3: {} + + dom-accessibility-api@0.5.16: {} + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.4.811: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + + es-get-iterator@1.1.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.3 + is-set: 2.0.3 + is-string: 1.0.7 + isarray: 2.0.5 + stop-iteration-iterator: 1.0.0 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.1.2: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.7 + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + flat@5.0.2: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.2.1: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + fraction.js@4.3.7: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.2: + dependencies: + foreground-child: 3.2.1 + jackspeak: 3.4.0 + minimatch: 9.0.4 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + + globals@11.12.0: {} + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + + graceful-fs@4.2.11: {} + + has-bigints@1.0.2: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + indent-string@4.0.0: {} + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 + + is-arguments@1.1.1: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-arrayish@0.3.2: {} + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.1.2: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.14.0: + dependencies: + hasown: 2.0.2 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-regex@1.1.4: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-weakmap@2.0.2: {} + + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + jackspeak@3.4.0: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.24.7 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.7 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.14.8 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jiti@1.21.6: {} + + js-tokens@4.0.0: {} + + jsesc@2.5.2: {} + + json5@2.2.3: {} + + lilconfig@2.1.0: {} + + lilconfig@3.1.2: {} + + lines-and-columns@1.2.4: {} + + lodash.foreach@4.5.0: {} + + lodash@4.17.21: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.2.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + min-indent@1.0.1: {} + + minimatch@9.0.4: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + ms@2.1.2: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.7: {} + + node-releases@2.0.14: {} + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.2: {} + + object-is@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + + object-keys@1.1.1: {} + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + package-json-from-dist@1.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.2.2 + minipass: 7.1.2 + + picocolors@1.0.1: {} + + picomatch@2.3.1: {} + + pify@2.3.0: {} + + pirates@4.0.6: {} + + possible-typed-array-names@1.0.0: {} + + postcss-import@15.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + postcss-js@4.0.1(postcss@8.4.38): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.38 + + postcss-load-config@4.0.2(postcss@8.4.38): + dependencies: + lilconfig: 3.1.2 + yaml: 2.4.5 + optionalDependencies: + postcss: 8.4.38 + + postcss-nested@6.0.1(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.1.0 + + postcss-selector-parser@6.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.38: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-refresh@0.14.2: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + regenerator-runtime@0.14.1: {} + + regexp.prototype.flags@1.5.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + resolve@1.22.8: + dependencies: + is-core-module: 2.14.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rollup@4.18.0: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.18.0 + '@rollup/rollup-android-arm64': 4.18.0 + '@rollup/rollup-darwin-arm64': 4.18.0 + '@rollup/rollup-darwin-x64': 4.18.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 + '@rollup/rollup-linux-arm-musleabihf': 4.18.0 + '@rollup/rollup-linux-arm64-gnu': 4.18.0 + '@rollup/rollup-linux-arm64-musl': 4.18.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 + '@rollup/rollup-linux-riscv64-gnu': 4.18.0 + '@rollup/rollup-linux-s390x-gnu': 4.18.0 + '@rollup/rollup-linux-x64-gnu': 4.18.0 + '@rollup/rollup-linux-x64-musl': 4.18.0 + '@rollup/rollup-win32-arm64-msvc': 4.18.0 + '@rollup/rollup-win32-ia32-msvc': 4.18.0 + '@rollup/rollup-win32-x64-msvc': 4.18.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + + slash@3.0.0: {} + + source-map-js@1.2.0: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stop-iteration-iterator@1.0.0: + dependencies: + internal-slot: 1.0.7 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.2 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.4: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.7 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.1 + postcss: 8.4.38 + postcss-import: 15.1.0(postcss@8.4.38) + postcss-js: 4.0.1(postcss@8.4.38) + postcss-load-config: 4.0.2(postcss@8.4.38) + postcss-nested: 6.0.1(postcss@8.4.38) + postcss-selector-parser: 6.1.0 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-interface-checker@0.1.13: {} + + tw-colors@3.3.1(tailwindcss@3.4.4): + dependencies: + color: 4.2.3 + flat: 5.0.2 + lodash.foreach: 4.5.0 + tailwindcss: 3.4.4 + + undici-types@5.26.5: {} + + update-browserslist-db@1.0.16(browserslist@4.23.1): + dependencies: + browserslist: 4.23.1 + escalade: 3.1.2 + picocolors: 1.0.1 + + use-sync-external-store@1.2.0(react@18.3.1): + dependencies: + react: 18.3.1 + + util-deprecate@1.0.2: {} + + vite@5.3.1(@types/node@20.14.8): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.38 + rollup: 4.18.0 + optionalDependencies: + '@types/node': 20.14.8 + fsevents: 2.3.3 + + which-boxed-primitive@1.0.2: + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.3 + + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + yallist@3.1.1: {} + + yaml@2.4.5: {} + + zustand@4.5.2(@types/react@18.3.3)(react@18.3.1): + dependencies: + use-sync-external-store: 1.2.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + react: 18.3.1 diff --git a/ui/postcss.config.js b/ui/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/ui/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/ui/public/.gitkeep b/ui/public/.gitkeep new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/ui/public/.gitkeep @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/ui/public/icon.svg b/ui/public/icon.svg new file mode 100644 index 0000000..5bec01e --- /dev/null +++ b/ui/public/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/public/screenshot-dark.png b/ui/public/screenshot-dark.png new file mode 100644 index 0000000..4e3387f Binary files /dev/null and b/ui/public/screenshot-dark.png differ diff --git a/ui/public/screenshot-light.png b/ui/public/screenshot-light.png new file mode 100644 index 0000000..c93ceab Binary files /dev/null and b/ui/public/screenshot-light.png differ diff --git a/ui/script.js b/ui/script.js deleted file mode 100644 index c95e4a5..0000000 --- a/ui/script.js +++ /dev/null @@ -1,315 +0,0 @@ -QB = {} -QB.Phone = {} -QB.Phone.Data = { - PlayerData: {}, -} -QB.Phone.Functions = {} - -QB.Phone.Functions.LoadPhoneData = function(data) { - QB.Phone.Data.PlayerData = data.PlayerData; -} - -var JoinPass = null; -var JoinID = null; - -function ClearInputNew(){ - $(".phone-new-input-class").val(""); -} - -function LoadJobCenterApp(){ - $.post('https://slrn_groups/GetGroupsApp', JSON.stringify({}), function(data){ - AddDIV(data) - }); -} - -$(document).ready(function(){ - window.addEventListener('message', function(event) { - switch(event.data.action) { - case "LoadPhoneData": - QB.Phone.Functions.LoadPhoneData(event.data); - break; - case "LoadJobCenterApp": - LoadJobCenterApp() - break; - case 'testPassword': - $('#jobcenter-box-new-join').fadeIn(350); - break; - case 'PhoneNotification': - setPopUp({ - title: event.data.PhoneNotify.title, - description: event.data.PhoneNotify.text, - buttons: [ - { - title: event.data.PhoneNotify.deny, - color: 'red', - cb: () => { - $.post('https://slrn_groups/AnsweredNotify', JSON.stringify({ - type: 'failure', - })); - } - }, - { - title: event.data.PhoneNotify.accept, - color: 'blue', - cb: () => { - $.post('https://slrn_groups/AnsweredNotify', JSON.stringify({ - type: 'success', - })); - } - } - ] - }); - break; - case 'SendNotify': - sendNotification({ title: event.data.msg }); - break; - - } - }) -}); - - -$(document).on('click', '.jobcenter-btn-create-group', function(e){ - e.preventDefault(); - ClearInputNew() - $('#jobcenter-box-new-dashboard').fadeIn(350); -}); - -$(document).on('click', '#box-new-cancel', function(e){ - e.preventDefault(); - ClearInputNew() - $('.phone-menu-body').fadeOut(350); - //$('.phone-new-box-body').fadeOut(350); -}); - -$(document).on('click', '#jobcenter-submit-create-group', function(e){ - e.preventDefault(); - var Name = $(".jobcenter-input-group-name").val(); - var pass = $(".jobcenter-input-password").val(); - var pass2 = $(".jobcenter-input-password2").val(); - if (Name != "" && pass != "" && pass2 != ""){ - if(pass == pass2){ - $.post('https://slrn_groups/jobcenter_CreateJobGroup', JSON.stringify({ - name: Name, - pass: pass, - })); - - - - - $('#jobcenter-box-new-dashboard').fadeOut(350); - }else{ - sendNotification({ title: 'The password entered is incorrect' }); - } - }else{ - sendNotification({ title: 'Fields are incorrect' }); - } -}); - -$(document).ready(function(){ - window.addEventListener('message', function(event) { - switch(event.data.action) { - case "refreshApp": - $(".jobcenter-list").css({"display": "inline"}); - $(".jobcenter-btn-create-group").css({"display": "inline"}); - $(".title").css({"display": "block"}); - $(".jobcenter-Groupjob").css({"margin-top": "47%"}); - AddDIV(event.data.data) - break; - case "addGroupStage": - AddGroupJobs(event.data.status) - break; - } - }) -}); - -$(document).ready(function(){ - window.addEventListener('message', function(event) { - switch(event.data.action) { - case "GroupAddDIV": - if(event.data.showPage && event.data.job != "WAITING"){ - AddGroupJobs(event.data.stage) - } else { - AddDIV(event.data.data) - } - break; - } - }) -}); - -function AddDIV(data){ - var AddOption; - var CSN = QB.Phone.Data.PlayerData.source; - $(".jobcenter-list").html(""); - if(data) { - Object.keys(data).map(function(element,index){ - if(data[element].leader == CSN) { - AddOption = ` -
-
- -
- ${data[element].GName} -
- - - ${data[element].Users}
- ` - } else { - AddOption = ` -
-
-
-
${data[element].GName}
- ${data[element].Users} -
- ` - Object.keys(data[element].members).map(function(element2, _){ - if(data[element].members[element2].Player == CSN) { - AddOption = ` -
-
- -
- ${data[element].GName} - -
- - ${data[element].Users}
- ` - } - }) - } - $('.jobcenter-list').append(AddOption); - }) - } else { - $(".jobcenter-list").html(""); - var AddOption = '
No Group
' - $('.jobcenter-list').append(AddOption); - } -} - -function AddGroupJobs(data){ - var AddOption; - $(".jobcenter-Groupjob").html(""); - $(".jobcenter-Groupjob").css({"margin-top": "23%"}); - $(".jobcenter-list").html(""); - $(".jobcenter-list").css({"display": "none"}); - $(".jobcenter-btn-create-group").css({"display": "none"}); - $(".title").css({"display": "none"}); - $(".jobcenter-groupjob-timer").css({"display": "block"}); - if(data) { - - - for (const [k, v] of Object.entries(data)) { - if (v.isDone) { - AddOption = - ` -
-

1 / 1

- ${v.name} -
- ` - } else { - AddOption = - ` -
-

0 / 1

- ${v.name} -
- ` - } - $('.jobcenter-Groupjob').append(AddOption); - } - } else { - $(".jobcenter-list").css({"display": "block"}); - $(".jobcenter-btn-create-group").css({"display": "block"}); - $(".title").css({"display": "block"}); - } -} - -$(document).on('click', '#jobcenter-delete-group', function(e){ - e.preventDefault(); - var Delete = $(this).data('delete') - $.post('https://slrn_groups/jobcenter_DeleteGroup', JSON.stringify({ - delete: Delete, - })); -}); - -$(document).on('click', '#jobcenter-join-grouped', function(e){ - e.preventDefault(); - JoinPass = $(this).data('pass') - JoinID = $(this).data('id') - ClearInputNew() - $('#jobcenter-box-new-join').fadeIn(350); -}); - -$(document).on('click', '#jobcenter-submit-join-group', function(e){ - e.preventDefault(); - var EnterPass = $(".jobcenter-input-join-password").val(); - if(EnterPass == JoinPass){ - var CSN = QB.Phone.Data.PlayerData.citizenid; - $.post('https://slrn_groups/jobcenter_JoinTheGroup', JSON.stringify({ - PCSN: CSN, - id: JoinID, - })); - ClearInputNew() - $('#jobcenter-box-new-join').fadeOut(350); - } -}); - -$(document).on('click', '#jobcenter-list-group', function(e){ - e.preventDefault(); - var id = $(this).data('id') - $.post('https://slrn_groups/jobcenter_CheckPlayerNames', JSON.stringify({ - id: id, - }), function(Data){ - ClearInputNew() - $('#jobcenter-box-new-player-name').fadeIn(350); - $("#phone-new-box-main-playername").html(""); - for (const [k, v] of Object.entries(Data)) { - var AddOption = `
${v}
` - - $('#phone-new-box-main-playername').append(AddOption); - } - - var AddOption2 = '

' - - $('#phone-new-box-main-playername').append(AddOption2); - }); -}); - -$(document).on('click', '#jobcenter-leave-grouped', function(e){ - e.preventDefault(); - var CSN = QB.Phone.Data.PlayerData.citizenid; - var id = $(this).data('id') - $.post('https://slrn_groups/jobcenter_leave_grouped', JSON.stringify({ - id: id, - csn: CSN, - })); -}); - -var buttonStart = document.getElementById('button-start'); -var buttonStop = document.getElementById('button-stop'); -var buttonReset = document.getElementById('button-reset'); - -onSettingsChange((settings) => { - let theme = settings.display.theme; - document.getElementsByClassName('app')[0].dataset.theme = theme; -}); - -getSettings().then((settings) => { - let theme = settings.display.theme; - document.getElementsByClassName('app')[0].dataset.theme = theme; -}); - - -$.post('https://slrn_groups/onStartup'); diff --git a/ui/src/App.css b/ui/src/App.css new file mode 100644 index 0000000..4d1ed36 --- /dev/null +++ b/ui/src/App.css @@ -0,0 +1,18 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Do not edit, this allows you to view changes easily in your browser */ +.dev-wrapper { + position: absolute; + + bottom: 0; + top: 0; + left: 0; + right: 0; + + margin: auto; + + width: 29rem; + height: 58.5rem; +} diff --git a/ui/src/App.tsx b/ui/src/App.tsx new file mode 100644 index 0000000..a8c11f1 --- /dev/null +++ b/ui/src/App.tsx @@ -0,0 +1,130 @@ +import React, { useEffect, useRef, useState } from "react"; +import GroupDashboard from "./components/GroupDashboard"; +import PlayerList from "./components/PlayerList"; +import GroupJob from "./components/GroupJob"; +import DataHandler from "./components/DataHandler"; +import { GroupJobStep } from "./types/GroupJobStep"; +import { Group } from "./types/Group"; +import { useNuiEvent } from "./hooks/useNuiEvent"; +import { useGroupStore } from "./storage/GroupStore"; +import { usePlayerDataStore } from "./storage/PlayerDataStore"; +import "./App.css"; + +const devMode = !window?.["invokeNative"]; + +const App = () => { + const [theme, setTheme] = useState("light"); + const appDiv = useRef(null); + + const { + setPopUp, + fetchNui, + sendNotification, + getSettings, + onSettingsChange, + } = window as any; + const [currentPage, setCurrentPage] = useState("GroupDashboard"); + const { inGroup, currentGroup } = useGroupStore(); + const { playerData } = usePlayerDataStore(); + + useEffect(() => { + if (devMode) { + document.getElementsByTagName("html")[0].style.visibility = "visible"; + document.getElementsByTagName("body")[0].style.visibility = "visible"; + return; + } else { + getSettings().then((settings: any) => setTheme(settings.display.theme)); + onSettingsChange((settings: any) => setTheme(settings.display.theme)); + } + }, [theme]); + + useEffect(() => { + if (!inGroup) { + setCurrentPage("GroupDashboard"); + } + }, [inGroup]); + + useNuiEvent("startJob", () => { + setCurrentPage("GroupJob"); + }); + + useNuiEvent("sendNotification", (data: any) => { + sendNotification(data); + }); + + useNuiEvent("phoneNotification", (data: any) => { + setPopUp({ + title: data.PhoneNotify.title, + description: data.PhoneNotify.text, + buttons: [ + { + title: data.PhoneNotify.deny, + color: "red", + cb: () => { + $.post( + "https://slrn_groups/AnsweredNotify", + JSON.stringify({ + type: "failure", + }) + ); + }, + }, + { + title: data.PhoneNotify.accept, + color: "blue", + cb: () => { + $.post( + "https://slrn_groups/AnsweredNotify", + JSON.stringify({ + type: "success", + }) + ); + }, + }, + ], + }); + }); + + const toggleTheme = () => { + setTheme(theme === "dark" ? "light" : "dark"); + }; + + return ( + +
+ {devMode && ( + + )} +
 
+
Groups
+ {currentPage === "GroupDashboard" && ( + + )} + {currentPage === "GroupJob" && ( + + )} +
+ +
+ ); +}; + +const AppProvider: React.FC = ({ children }) => { + if (devMode) { + return ( +
+ {children} +
+ ); + } else return children; +}; + +export default App; diff --git a/ui/src/components/ConfirmationDialog.tsx b/ui/src/components/ConfirmationDialog.tsx new file mode 100644 index 0000000..571f01c --- /dev/null +++ b/ui/src/components/ConfirmationDialog.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +const ConfirmationDialog = ({ onClose, onConfirm, confirmation}) => { + return ( +
+
+

+ {confirmation.message} +

+
+ + +
+
+
+ ); +}; + +export default ConfirmationDialog; diff --git a/ui/src/components/CreateGroup.tsx b/ui/src/components/CreateGroup.tsx new file mode 100644 index 0000000..eeb3499 --- /dev/null +++ b/ui/src/components/CreateGroup.tsx @@ -0,0 +1,109 @@ +import React, { useState, useEffect } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; + +const CreateGroup: React.FC = ({ onSelect, onClose }) => { + const [groupName, setGroupName] = useState(""); + const [disabledReason, setDisabledReason] = useState(""); + const [password, setPassword] = useState(""); + const [verifyPassword, setVerifyPassword] = useState(""); + const [isSubmitDisabled, setIsSubmitDisabled] = useState(true); + + useEffect(() => { + let reason = ""; + if (groupName === "") { + reason = "Group name is required"; + } else if (password === "") { + reason = "Password is required"; + } else if (password !== verifyPassword) { + reason = "Passwords do not match"; + } + setDisabledReason(reason); + setIsSubmitDisabled(reason !== ""); + }, [password, verifyPassword, groupName]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const groupData = { groupName, password }; + onSelect(groupData); + }; + + return ( +
+
+
+

Create Group

+ +
+
+
+ + setGroupName(e.target.value)} + className="w-full p-2 rounded bg-secondary border dark:border-none border-secondary" + /> +
+
+ + setPassword(e.target.value)} + className="w-full p-2 rounded bg-secondary border dark:border-none border-secondary" + /> +
+
+ + setVerifyPassword(e.target.value)} + className="w-full p-2 rounded bg-secondary border dark:border-none border-secondary" + /> +
+
+ + +
+
+ {disabledReason ? disabledReason : String.fromCharCode(160)} +
+
+
+
+ ); +}; + +export default CreateGroup; diff --git a/ui/src/components/DataHandler.tsx b/ui/src/components/DataHandler.tsx new file mode 100644 index 0000000..db716a2 --- /dev/null +++ b/ui/src/components/DataHandler.tsx @@ -0,0 +1,46 @@ +import React, { useEffect } from "react"; +import { useNuiEvent } from "../hooks/useNuiEvent"; +import { usePlayerDataStore } from "../storage/PlayerDataStore"; +import { useGroupStore } from "../storage/GroupStore"; +import { useGroupJobStepStore } from "../storage/GroupJobStepStore"; + +const DataHandler: React.FC = () => { + const { setPlayerData } = usePlayerDataStore(); + const { setGroups, setIsLeader, setCurrentGroup, setInGroup } = useGroupStore(); + const { setGroupJobSteps } = useGroupJobStepStore(); + + useNuiEvent("setPlayerData", setPlayerData); + useNuiEvent("setGroups", setGroups); + useNuiEvent("setGroupJobSteps", setGroupJobSteps); + useNuiEvent("setCurrentGroup", setCurrentGroup); + useNuiEvent("setInGroup", setInGroup); + useNuiEvent("updateGroupJobStep", (data: { id: string; isDone: boolean }) => { // might be unused now due to updates + if (!data || !data.id) { + console.error("Invalid updateGroupJobStep data", data); + return; + } + setGroupJobSteps((prevSteps) => + prevSteps.map((step) => + step.id === data.id ? { ...step, isDone: data.isDone } : step + ) + ); + }); + 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 +}; + +export default DataHandler; diff --git a/ui/src/components/GroupDashboard.tsx b/ui/src/components/GroupDashboard.tsx new file mode 100644 index 0000000..4296f14 --- /dev/null +++ b/ui/src/components/GroupDashboard.tsx @@ -0,0 +1,195 @@ +import React, { useState } from "react"; +import CreateGroup from "./CreateGroup"; +import JoinGroup from "./JoinGroup"; +import PlayerList from "./PlayerList"; +import ConfirmationDialog from "./ConfirmationDialog"; +import { Group } from "../types/Group"; +import { usePlayerDataStore } from "../storage/PlayerDataStore"; +import { useGroupStore } from "../storage/GroupStore"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faUsers, + faList, + faTrash, + faUserGroup, + faRightFromBracket, +} from "@fortawesome/free-solid-svg-icons"; + +const GroupDashboard = ({ setCurrentPage, fetchNui }) => { + const { currentGroups, currentGroup, inGroup, isLeader } = useGroupStore(); + const { playerData } = usePlayerDataStore(); + const [showCreateGroup, setShowCreateGroup] = useState(false); + const [showPlayerList, setShowPlayerList] = useState(false); + const [selectedGroup, setSelectedGroup] = useState(null); + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [confirmation, setConfirmation] = useState({ message: null, type: null }); + + const handleConfirm = () => { + fetchNui(confirmation.type); + setIsDialogOpen(false); + }; + + const createGroup = (groupData) => { + fetchNui("createGroup", {name: groupData.groupName, pass: groupData.password}); + }; + + const joinGroup = (groupData) => { + fetchNui("joinGroup", {id: groupData.groupId, pass: groupData.password}); + }; + + const leaveGroup = () => { + setConfirmation({message: "Leave the group?", type: "leaveGroup"}) + setIsDialogOpen(true); + }; + + const deleteGroup = (groupData) => { + setConfirmation({message: "Disband the group?", type: "deleteGroup"}) + setIsDialogOpen(true); + }; + + const renderIcons = (isLeader, isMember, group) => { + return ( + <> +
+ setShowPlayerList(true)} + /> + {isLeader && ( + deleteGroup(group)} + /> + )} + {isMember && !isLeader && ( + leaveGroup(group)} + size="xl" + /> + )} +
+ + ); + }; + + return ( +
+
+
+ + + +
+

+ {currentGroups?.length > 0 ? ( + 'Create a group or join an existing group below' + ) : ( + 'Create a group to get started' + )} +

+ {currentGroups && currentGroups.length > 0 && ( + <> + {currentGroups.map((group, index) => { + let isMember = group.id === inGroup; + + return ( + //
+
{ + if (!inGroup) { + setSelectedGroup(group); + } + }} + > +
+ + {group.name} +
+
+ <> + {isMember && renderIcons(isLeader, isMember, group)} + + + {group.memberCount} + + +
+
+ //
+ ); + })} + + )} + {showCreateGroup && ( + { + createGroup(groupData); + setShowCreateGroup(false); + }} + onClose={() => setShowCreateGroup(false)} + /> + )} + {selectedGroup && ( + { + joinGroup(groupData); + setSelectedGroup(null); + }} + onClose={() => { + setSelectedGroup(null); + }} + /> + )} + {showPlayerList && ( + setShowPlayerList(false)} + fetchNui={fetchNui} + currentGroup={currentGroup} + /> + )} + {isDialogOpen && ( + setIsDialogOpen(false)} + onConfirm={handleConfirm} + confirmation={confirmation} + /> + )} +
+
+ ); +}; + +export default GroupDashboard; diff --git a/ui/src/components/GroupJob.tsx b/ui/src/components/GroupJob.tsx new file mode 100644 index 0000000..4df1d1b --- /dev/null +++ b/ui/src/components/GroupJob.tsx @@ -0,0 +1,105 @@ +import React, { useState, useEffect } from "react"; +import ConfirmationDialog from "./ConfirmationDialog"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCircle } from "@fortawesome/free-solid-svg-icons"; +import { GroupJobStep } from "../types/GroupJobStep"; +import { useGroupJobStepStore } from "../storage/GroupJobStepStore"; +import { useGroupStore } from "../storage/GroupStore"; +import { usePlayerDataStore } from "../storage/PlayerDataStore"; + +interface GroupJobProps { + initialSteps: GroupJobStep[]; +} + +const { fetchNui } = window as any; + +const GroupJob: React.FC = ({ setCurrentPage, fetchNui }) => { + const { currentGroups, currentGroup, inGroup } = useGroupStore(); + const { playerData } = usePlayerDataStore(); + const { groupJobSteps, setGroupJobSteps } = useGroupJobStepStore(); + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [confirmation, setConfirmation] = useState({ + message: null, + type: null, + }); + + useEffect(() => { + fetchNui("getGroupJobSteps").then((data) => { + if (data) { + setGroupJobSteps(data); + } + }); + }, []); + + const handleConfirm = () => { + fetchNui(confirmation.type); + setIsDialogOpen(false); + }; + + const leaveGroup = () => { + setConfirmation({ message: "Leave the group?", type: "leaveGroup" }); + setIsDialogOpen(true); + }; + + return ( +
+
+
+ + +
+ + {groupJobSteps && groupJobSteps.length > 0 + ? "Here are the current group tasks" + : "No tasks available"} + +
+
+ {groupJobSteps && groupJobSteps.map((step, index) => ( +
+ + + +
+ {/*
+ {step.isDone ? "1 / 1" : "0 / 1"} +
*/} +
{step.name}
+
+
+ ))} +
+
+ {isDialogOpen && ( + setIsDialogOpen(false)} + onConfirm={handleConfirm} + confirmation={confirmation} + /> + )} +
+
+ ); +}; + +export default GroupJob; diff --git a/ui/src/components/JoinGroup.tsx b/ui/src/components/JoinGroup.tsx new file mode 100644 index 0000000..b56e4a0 --- /dev/null +++ b/ui/src/components/JoinGroup.tsx @@ -0,0 +1,94 @@ +import React, { useState, useEffect } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; + +const JoinGroup: React.FC = ({ + groupId, + groupName, + onSelect, + onClose, +}) => { + const [password, setPassword] = useState(""); + const [disabledReason, setDisabledReason] = useState(""); + const [isSubmitDisabled, setIsSubmitDisabled] = useState(true); + + useEffect(() => { + let reason = ""; + if (password === "") { + reason = "Password is required"; + } + setDisabledReason(reason); + setIsSubmitDisabled(reason !== ""); + }, [password]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const groupData = { groupId, password }; + onSelect(groupData); + }; + + return ( +
+
+
+

+ Join Group +

+ +
+
+
+ +
+
+ + setPassword(e.target.value)} + className="w-full p-2 rounded bg-secondary border border-secondary dark:border-none text-2xl" + /> +
+
+ + +
+
+ {disabledReason ? disabledReason : String.fromCharCode(160)} +
+
+
+
+ ); +}; + +export default JoinGroup; diff --git a/ui/src/components/PlayerList.tsx b/ui/src/components/PlayerList.tsx new file mode 100644 index 0000000..2dc92be --- /dev/null +++ b/ui/src/components/PlayerList.tsx @@ -0,0 +1,72 @@ +import React, { useContext, useEffect } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faUser, faTrash, faCrown } from "@fortawesome/free-solid-svg-icons"; +import { usePlayerDataStore } from "../storage/PlayerDataStore"; +import { useGroupStore } from "../storage/GroupStore"; + +const PlayerList: React.FC = ({ onClose, fetchNui }) => { + const { playerData } = usePlayerDataStore(); + const { currentGroup, isLeader } = useGroupStore(); + + const removeGroupMember = (member) => { + fetchNui("removeGroupMember", member.playerId); + }; + + useEffect(() => { + if (!currentGroup || currentGroup.length === 0) { + onClose(); + } + }, [currentGroup]); + + return ( +
+
+
+

Group Members

+

{currentGroup.name}

+
+
+ {currentGroup.map((member, index) => { + return ( +
+ + <> + {isLeader && + member.playerId !== + playerData.source && ( + <> + + removeGroupMember( + member + ) + } + /> + + )} + + {member.name} +
+ ); + })} +
+
+ +
+
+
+ ); +}; + +export default PlayerList; diff --git a/ui/src/hooks/useNuiEvent.ts b/ui/src/hooks/useNuiEvent.ts new file mode 100644 index 0000000..d5d3d9a --- /dev/null +++ b/ui/src/hooks/useNuiEvent.ts @@ -0,0 +1,49 @@ +import { MutableRefObject, useEffect, useRef } from 'react'; +import { noop } from '../utils/misc'; + +interface NuiMessageData { + action: string; + data: T; +} + +type NuiHandlerSignature = (data: T) => void; + +/** + * A hook that manage events listeners for receiving data from the client scripts + * @param action The specific `action` that should be listened for. + * @param handler The callback function that will handle data relayed by this hook + * + * @example + * useNuiEvent<{visibility: true, wasVisible: 'something'}>('setVisible', (data) => { + * // whatever logic you want + * }) + * + **/ + +export const useNuiEvent = ( + action: string, + handler: (data: T) => void, +) => { + const savedHandler: MutableRefObject> = useRef(noop); + + // Make sure we handle for a reactive handler + useEffect(() => { + savedHandler.current = handler; + }, [handler]); + + useEffect(() => { + const eventListener = (event: MessageEvent>) => { + const { action: eventAction, data } = event.data; + + if (savedHandler.current) { + if (eventAction === action) { + savedHandler.current(data); + } + } + }; + + window.addEventListener('message', eventListener); + // Remove Event Listener on component cleanup + return () => window.removeEventListener('message', eventListener); + }, [action]); +}; diff --git a/ui/src/index.css b/ui/src/index.css new file mode 100644 index 0000000..895fa15 --- /dev/null +++ b/ui/src/index.css @@ -0,0 +1,8 @@ +html, +body { + margin: 0; + padding: 0; + + box-sizing: border-box; + visibility: hidden; /* DO NOT CHANGE, IMPORTANT!*/ +} diff --git a/ui/src/index.tsx b/ui/src/index.tsx new file mode 100644 index 0000000..46cee71 --- /dev/null +++ b/ui/src/index.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' + +import './index.css' + +const devMode = !window?.['invokeNative'] +const root = ReactDOM.createRoot(document.getElementById('root')) + +if (window.name === '' || devMode) { + const renderApp = () => { + root.render( + + + + ) + } + + if (devMode) { + renderApp() + } else { + window.addEventListener('message', (event) => { + if (event.data === 'componentsLoaded') renderApp() + }) + } +} diff --git a/ui/src/storage/GroupJobStepStore.ts b/ui/src/storage/GroupJobStepStore.ts new file mode 100644 index 0000000..bc5b18f --- /dev/null +++ b/ui/src/storage/GroupJobStepStore.ts @@ -0,0 +1,12 @@ +import { create } from 'zustand'; +import { GroupJobStep } from '../types/GroupJobStep'; + +interface GroupJobStepStore { + groupJobSteps: GroupJobStep[]; + setGroupJobSteps: (groupJobSteps: GroupJobStep[]) => void; +} + +export const useGroupJobStepStore = create((set) => ({ + groupJobSteps: [], + setGroupJobSteps: (data) => set({ groupJobSteps: data }), +})); \ No newline at end of file diff --git a/ui/src/storage/GroupStore.ts b/ui/src/storage/GroupStore.ts new file mode 100644 index 0000000..343652b --- /dev/null +++ b/ui/src/storage/GroupStore.ts @@ -0,0 +1,34 @@ +import { create } from "zustand"; +import { Group } from "../types/Group"; +import { Member } from "../types/Member"; +import { GroupJobStep } from "../types/GroupJobStep"; + +interface GroupStore { + currentGroups: Group[]; + currentGroup: Member[]; + inGroup: number | null; + isLeader: boolean; + setGroups: (currentGroups: Group[]) => void; + setCurrentGroup: (currentGroup: Group) => void; + setInGroup: (inGroup: boolean) => void; + setIsLeader: (isLeader: boolean) => void; +} + +export const useGroupStore = create((set) => ({ + currentGroups: [], + currentGroup: [], + inGroup: false, + isLeader: false, + setGroups: (data) => { + set({ currentGroups: data }); + }, + setCurrentGroup: (data) => { + set({ currentGroup: data }); + }, + setInGroup: (data) => { + set({ inGroup: data }); + }, + setIsLeader: (data) => { + set({ isLeader: data }); + }, +})); \ No newline at end of file diff --git a/ui/src/storage/PlayerDataStore.ts b/ui/src/storage/PlayerDataStore.ts new file mode 100644 index 0000000..15805ae --- /dev/null +++ b/ui/src/storage/PlayerDataStore.ts @@ -0,0 +1,17 @@ +import { create } from 'zustand'; + +interface PlayerData { + source: number; +} + +interface PlayerDataStore { + playerData: PlayerData; + setPlayerData: (data: PlayerData) => void; +} + +export const usePlayerDataStore = create((set) => ({ + playerData: null, + setPlayerData: (data) => { + set({ playerData: data }); + }, +})); diff --git a/ui/src/storage/PlayerListStore.ts b/ui/src/storage/PlayerListStore.ts new file mode 100644 index 0000000..463a811 --- /dev/null +++ b/ui/src/storage/PlayerListStore.ts @@ -0,0 +1,35 @@ +import { create } from 'zustand'; + +type PlayerListStoreState = { + members: string[]; + leader: number; + addMember: (member: string) => void; + setLeader: (leader: number) => void; +}; + +const usePlayerListStore = create((set) => ({ + members: [], + leader: 0, + addMember: (member) => set((state) => ({ members: [...state.members, member] })), + setLeader: (leader) => set({ leader }), +})); + +const PlayerListStore: React.FC = () => { + const { members, leader, addMember, setLeader } = usePlayerListStore(); + + return ( +
+

Player List

+
    + {members.map((member, index) => ( +
  • {member}
  • + ))} +
+

Leader: {leader}

+ + +
+ ); +}; + +export default PlayerListStore; \ No newline at end of file diff --git a/ui/src/types/Group.ts b/ui/src/types/Group.ts new file mode 100644 index 0000000..8102528 --- /dev/null +++ b/ui/src/types/Group.ts @@ -0,0 +1,10 @@ +export type Group = { + id: number; + status: string; + GName: string; + GPass: string; + leader: number; + members: Member[]; + stage: GroupJobStep[]; + ScriptCreated: boolean; +}; diff --git a/ui/src/types/GroupJobStep.ts b/ui/src/types/GroupJobStep.ts new file mode 100644 index 0000000..ba192b4 --- /dev/null +++ b/ui/src/types/GroupJobStep.ts @@ -0,0 +1,5 @@ +export type GroupJobStep = { + id: number; + name: string; + isDone: boolean; +}; diff --git a/ui/src/types/Member.ts b/ui/src/types/Member.ts new file mode 100644 index 0000000..7802345 --- /dev/null +++ b/ui/src/types/Member.ts @@ -0,0 +1,4 @@ +export type Member = { + name: string; + playerId: number; +}; \ No newline at end of file diff --git a/ui/src/utils/misc.ts b/ui/src/utils/misc.ts new file mode 100644 index 0000000..f0a087d --- /dev/null +++ b/ui/src/utils/misc.ts @@ -0,0 +1,6 @@ +// Will return whether the current environment is in a regular browser +// and not CEF +export const isEnvBrowser = (): boolean => !(window as any).invokeNative; + +// Basic no operation function +export const noop = () => {}; diff --git a/ui/styles.css b/ui/styles.css deleted file mode 100644 index 43557ee..0000000 --- a/ui/styles.css +++ /dev/null @@ -1,443 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200&display=swap'); - - -@font-face { - font-family: 'password'; - font-style: normal; - src: url(https://jsbin-user-assets.s3.amazonaws.com/rafaelcastrocouto/password.ttf); -} - -input.jobcenter-input-password, input.jobcenter-input-password2 { - font-family: 'password'; -} - -#phone-menu-icon{ - position: relative; - top: 0.2vh; - right: 26.2vh; - font-size: 1.4vh; - color: white; -} - -html, -body { - margin: 0; - padding: 0; - - box-sizing: border-box; - visibility: hidden; - - font-family: 'Poppins', sans-serif; -} - -.app { - width: 100%; - height: 100%; - - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - - background-color: var(--background-primary); -} - -.app-wrapper { - width: 100%; - height: 100%; - - display: flex; - flex-direction: column; - align-items: start; - justify-content: center; - gap: 3rem; -} - -.header { - position: absolute; - width: 90%; - height: 10%; - /* top: 8%; */ - top: 8%; - left: 6%; - right: 0; - color: white; - /* font-family: 'Poppins', sans-serif; */ - font-size: 1.3vh; - /* margin: 0 auto; */ -} - -.jobcenter-header { - position: absolute; - width: 90%; - height: 10%; - /* top: 8%; */ - top: 19%; - left: 6%; - right: 0; - color: white; - /* font-family: 'Poppins', sans-serif; */ - font-size: 2.0vh; - /* margin: 0 auto; */ -} - -.jobcenter-btn-create-group{ - display: inline; - background: #8ee074; - padding: 2% 4%; - transition: 0.2s; - border-radius: 4px; - color: black; - cursor: pointer; -} -.jobcenter-btn-create-group:hover{ - background: #6cac59; -} - -.button-wrapper { - display: flex; - flex-direction: column; - text-align: center; - gap: 0.2rem; -} - -.button-wrapper { - gap: 1rem; -} - -.title { - font-size: 20px; - font-weight: 700; - color: var(--text-primary); -} - -/* Do not edit, this allows you to view changes easily in your browser */ -#phone-wrapper { - display: none; - - position: absolute; - bottom: 0; - top: 0; - left: 0; - right: 0; - - margin: auto; - - width: 29rem; - height: 58.5rem; -} - -.phone-menu-text{ - position: relative; - margin-left: 10%; - margin-bottom: -10%; - width: 80%; - border: none; - background: none; - outline: none; - text-indent: 3vh; - text-align: left; - line-height: 3.0vh; - font-size: 1.75vh; - overflow: hidden; - color: white; - border-bottom: .1vh solid #fff; - transition: 0.25s; -} - -.phone-menu-main{ - position: absolute; - transform: translate(-50%, -50%); - top: 50%; - left: 50%; - padding-top: 10%; - padding-bottom: 5%; - width: 73%; - background-color: rgb(59, 59, 59); -} - -.phone-menu-title{ - position: relative; - left: 3.3vh; - font-size: 1.2vh; - color: white; -} - -.phone-menu-body{ - display: none; - position: absolute; - transform: translate(-50%, -50%); - top: 50%; - left: 50%; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.624); - z-index: 700; -} - -.phone-menu-button{ - display: inline; - transition: 0.25s; - padding: 2%; - border-radius: 5px; - color: black; - font-family: 'Roboto', sans-serif; - font-size: 1.4vh; - margin-bottom: 4%; -} - -.phone-menu-accept{ - background:#90e278; - margin-left: 25%; - padding: 4.0%; - cursor: pointer; -} -.phone-menu-accept:hover{ - background: #6cac59; -} - -.phone-menu-cancel{ - background:#f6a167; - margin-left: 15%; - padding: 4.0%; - cursor: pointer; -} -.phone-menu-cancel:hover{ - background: #c27e47; -} - -.phone-new-box-main input::-webkit-inner-spin-button { - -webkit-appearance: none; -} - -#phone-new-box-main-playername { - width: 86.3%; - margin-left: 6.5%; -} - -.jobcenter-playerlist { - width: 100%; - height: 100%; - background-color: #242833; - bottom: 0%; - position: absolute; - overflow: hidden; -} - -.casino-text-clear{ - text-align: center; - color: whitesmoke; -} - -.jobcenter-playerlist2 { - width: 100%; - height: 60.5%; - background-color: #242833; - bottom: 0; - position: relative; -} - -.jobcenter-div-active-stagee { - width: 100%; - padding: 10px; - display: flex; - align-items: center; - gap: 10px; - color: white; - font-size: 1.5vh; - position: relative; - height: auto; - word-break: break-word; - } - -.jobcenter-playerlist-list { - height: 4vh; - font-size: 2vh; - border-bottom: 1px white solid; - background: #3D6285; -} - -.jobcenter-playerlist-name { - color: white; - /* background-color: black; */ - height: 100%; - font-size: 1.55vh; - text-align: left; - position: absolute; - left: 27%; - margin-top: 5px; -} - -.jobcenter-playerlist-header { - color: white; - margin-left: 6%; - margin-top: 14%; - font-size: 1.7vh; - margin-bottom: 5%; -} - -.jobcenter-playerlist-leave { - background: #95EF77; - margin-left: 36.5%; - padding: 2.8% 5%; - /* font-weight: 700; */ - bottom: 7.5%; - position: absolute; - cursor: pointer; - /* top: 0; */ -} - -.jobcenter-playerlist-leave:hover { - background: #629e4e; -} - -.isDone { - text-decoration: line-through; - background: #2c465f !important; - position: relative; - } - - .isDone::after { - height: 30px; - background: #008685 !important; - } - -.jobcenter-div-active-stagee::after { - content: ''; - height: 12px; - background: #4155C0; - left: -23px; - top: -3px; - width: 12px; - position: absolute; - border-radius: 100%; - } - - .jobcenter-div-active-stagee::before { - content: ''; - height: 90%; - background: white; - left: -18px; - top: 15px; - width: 2px; - position: absolute; - } - -.jobcenter-div-job-group{ - /* display: inline; */ - width: 98%; - margin-left: 1%; - margin-bottom: 2%; - padding-left: 5%; - background-color: #2c465f; - transition: 0.2s; - color: whitesmoke; - border-bottom: 2px solid #f5f5f5ad; - border-radius: 2px; -} - -.jobcenter-div-active-stage{ - width: 100%; - padding: 10px; - display: flex; - align-items: center; - gap: 10px; - color: white; - font-size: 1.5vh; - position: relative; - height: auto; - font: -webkit-control; -} - -.jobcenter-Groupjob { - margin-top: 16%; - width: 99%; - display: flex; - flex-direction: column; - gap: 23px; - padding-left: 9%; - margin-top: 47%; - border-top: 2px solid #7d7d7d; - width: 87%; - display: flex; - padding-top: 10px; - align-content: center; - align-items: center; - margin-left: 6%; - height: 70%; - overflow: auto; -} - -.jobcenter-groupjob-timer { - display: none; - width: 100%; - font-size: 25px; - top: 0px; - position: absolute; - color: white; - margin-top: 30px; - margin-left: 190px; -} - -.jobcenter-Groupjob::-webkit-scrollbar { - display: none; -} - -.jobcenter-Groupjob-background { - background: black; - width: 100%; - height: 100%; -} - -.jobcenter-div-job-group:hover{ - background-color: #2e4d6a; -} - -.jobcenter-div-job-group-image{ - font-size: 250%; - display: inline-block; -} - -.jobcenter-option-class-body{ - text-align: right; -} - -.jobcenter-div-job-group-body-main{ - display: inline-block; - position: absolute; - /* background: black; */ - width: 70%; - max-width: 70%; - margin-top: 4%; - margin-left: 4%; -} - -#jobcenter-list-group:hover{ - color: #8ee074; -} - -#jobcenter-delete-group:hover{ - color: #dc3333; -} - -.jobcenter-list { - position: absolute; - width: 88.8%; - margin: 0 auto; - left: -1%; - right: 0; - border-radius: .5vh; - overflow-y: scroll; - bottom: 0%; - height: 76%; -} - -.jobcenter-list::-webkit-scrollbar { - display: none; -} \ No newline at end of file diff --git a/ui/tailwind.config.js b/ui/tailwind.config.js new file mode 100644 index 0000000..021a6d8 --- /dev/null +++ b/ui/tailwind.config.js @@ -0,0 +1,38 @@ +/** @type {import('tailwindcss').Config} */ +const { createThemes } = require('tw-colors'); +module.exports = { + content: ["./src/**/*.{js,jsx,ts,tsx}"], + darkMode: ["selector", '[data-mode="dark"]'], + theme: { + fontFamily: { + sans: ["Graphik", "sans-serif"], + serif: ["Merriweather", "serif"], + }, + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [ + createThemes({ + light: { + text: '#030704', + background: '#fcfdfd', + primary: '#57b277', + secondary: '#a7b7d7', + accent: '#8981c5', + danger: '#ff0000', + success: '#8981c5', + }, + dark: { + text: '#f8fcf9', + background: '#020303', + primary: '#4da86d', + secondary: '#283858', + accent: '#423a7e', + danger: '#ff0000', + success: '#423a7e', + }, + }), + ], +}; diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 0000000..6d4c715 --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "esnext", + "paths": { "*": ["./*"] }, + "types": ["vite/client"], + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": false, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "jsxImportSource": "react", + "noEmit": false, + "sourceMap": true, + "outDir": "../build/", + "experimentalDecorators": true + }, + "exclude": ["node_modules"], + "include": ["src"] +} diff --git a/ui/vite.config.ts b/ui/vite.config.ts new file mode 100644 index 0000000..4ba0865 --- /dev/null +++ b/ui/vite.config.ts @@ -0,0 +1,24 @@ +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +// https://vitejs.dev/config/ +export default defineConfig(( {command} ) => ({ + base: command === 'build' ? '/ui/dist' : undefined, + define: { + global: 'window' + }, + build: { + sourcemap: false + }, + optimizeDeps: { + esbuildOptions: { + mainFields: ['module', 'main'], + resolveExtensions: ['.js', '.jsx'] + } + }, + server: { + port: 3000, + open: true + }, + plugins: [react()], +}));