refactor(resource/interface): formatting and type definitions

This commit is contained in:
Linden
2022-09-18 09:06:32 +10:00
parent cb645ae345
commit 6254ba9ba2
8 changed files with 388 additions and 265 deletions

View File

@@ -1,5 +1,13 @@
local alert = nil
---@class AlertDialogProps
---@field header string;
---@field content string;
---@field centered? boolean?;
---@field cancel? boolean?;
---@param data AlertDialogProps
---@return 'cancel' | 'confirm' | nil
function lib.alertDialog(data)
if alert then return end
alert = promise.new()
@@ -21,4 +29,4 @@ RegisterNUICallback('closeAlert', function(data, cb)
promise:resolve(data)
end)
RegisterNetEvent('ox_lib:alertDialog', lib.alertDialog)
RegisterNetEvent('ox_lib:alertDialog', lib.alertDialog)

View File

@@ -1,6 +1,7 @@
---@param value string
function lib.setClipboard(value)
SendNUIMessage({
action = 'setClipboard',
data = value
})
end
SendNUIMessage({
action = 'setClipboard',
data = value
})
end

View File

@@ -1,16 +1,41 @@
local contextMenus = {}
local openContextMenu = nil
---@class ContextMenuItem
---@field title? string
---@field menu? string
---@field icon? string
---@field iconColor? string
---@field onSelect? fun(args: any)
---@field arrow? boolean
---@field description? string
---@field metadata? string | { [string]: any } | string[]
---@field event? string
---@field serverEvent? string
---@field args? any
---@class ContextMenuArrayItem : ContextMenuItem
---@field title string
---@class ContextMenuProps
---@field id string
---@field title string
---@field menu? string
---@field onExit? fun()
---@field canClose? boolean
---@field options { [string]: ContextMenuItem } | ContextMenuArrayItem[]
local function closeContext(_, cb, onExit)
if cb then cb(1) end
if (cb or onExit) and contextMenus[openContextMenu].onExit then contextMenus[openContextMenu].onExit() end
SetNuiFocus(false, false)
if not cb then SendNUIMessage({action = 'hideContext'}) end
if not cb then SendNUIMessage({ action = 'hideContext' }) end
openContextMenu = nil
end
---@param id string
function lib.showContext(id)
if not contextMenus[id] then return error('No context menu of such id found.') end
if not contextMenus[id] then error('No context menu of such id found.') end
local data = contextMenus[id]
openContextMenu = id
SetNuiFocus(true, true)
@@ -25,6 +50,7 @@ function lib.showContext(id)
}, { sort_keys = true }))
end
---@param context ContextMenuProps | ContextMenuProps[]
function lib.registerContext(context)
for k, v in pairs(context) do
if type(k) == 'number' then
@@ -36,9 +62,11 @@ function lib.registerContext(context)
end
end
---@return string?
function lib.getOpenContextMenu() return openContextMenu end
function lib.hideContext(onExit) return closeContext(nil, nil, onExit) end
---@param onExit boolean?
function lib.hideContext(onExit) closeContext(nil, nil, onExit) end
RegisterNUICallback('openContext', function(id, cb)
cb(1)
@@ -64,4 +92,4 @@ RegisterNUICallback('clickContext', function(id, cb)
})
end)
RegisterNUICallback('closeContext', closeContext)
RegisterNUICallback('closeContext', closeContext)

View File

@@ -1,33 +1,49 @@
local input
---@class InputDialogRowProps
---@field type 'input' | 'number' | 'checkbox' | 'select' | 'slider'
---@field label string
---@field options? { value: string, label: string, default?: string }[]
---@field password? boolean
---@field icon? string
---@field iconColor? string
---@field placeholder? string
---@field default? string | number
---@field checked? boolean
---@field min? number
---@field max? number
---@field step? number
---@param heading string
---@param rows string[] | InputDialogRowProps[]
---@return string[] | number[] | boolean[] | nil
function lib.inputDialog(heading, rows)
if input then return end
input = promise.new()
if input then return end
input = promise.new()
-- Backwards compat with string tables
for i = 1, #rows do
if type(rows[i]) == 'string' then
rows[i] = {type = 'input', label = rows[i]}
end
-- Backwards compat with string tables
for i = 1, #rows do
if type(rows[i]) == 'string' then
rows[i] = { type = 'input', label = rows[i] --[[@as string]] }
end
end
end
SetNuiFocus(true, true)
SendNUIMessage({
action = 'openDialog',
data = {
heading = heading,
rows = rows
}
})
SetNuiFocus(true, true)
SendNUIMessage({
action = 'openDialog',
data = {
heading = heading,
rows = rows
}
})
return Citizen.Await(input)
return Citizen.Await(input)
end
RegisterNUICallback('inputData', function(data, cb)
cb(1)
SetNuiFocus(false, false)
local promise = input
input = nil
promise:resolve(data)
end)
cb(1)
SetNuiFocus(false, false)
local promise = input
input = nil
promise:resolve(data)
end)

View File

@@ -2,139 +2,169 @@ local registeredMenus = {}
local openMenu = nil
local keepInput = IsNuiFocusKeepingInput()
---@alias MenuPosition 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
---@alias MenuChangeFunction fun(selected: number, scrollIndex?: number, args?: any)
---@class MenuOptions
---@field label string
---@field icon? string
---@field values? string[]
---@field description? string
---@field defaultIndex? number
---@field args? any
---@class MenuProps
---@field id string
---@field title string
---@field options MenuOptions[]
---@field position? MenuPosition
---@field disableInput? boolean
---@field onClose? fun()
---@field onSelected? MenuChangeFunction
---@field onSideScroll? MenuChangeFunction
---@param data MenuProps
---@param cb? MenuChangeFunction
function lib.registerMenu(data, cb)
if not data.id then return error('No menu id was provided.') end
if not data.title then return error('No menu title was provided.') end
if not data.options then return error('No menu options were provided.') end
data.cb = cb
registeredMenus[data.id] = data
if not data.id then error('No menu id was provided.') end
if not data.title then error('No menu title was provided.') end
if not data.options then error('No menu options were provided.') end
data.cb = cb
registeredMenus[data.id] = data
end
---@param id string
---@param startIndex? number
function lib.showMenu(id, startIndex)
local menu = registeredMenus[id]
local menu = registeredMenus[id]
if not menu then
return error(('No menu with id %s was found'):format(id))
end
if not menu then
error(('No menu with id %s was found'):format(id))
end
if not openMenu then
CreateThread(function()
while openMenu do
if not openMenu.disableInput then
DisablePlayerFiring(cache.playerId, true)
HudWeaponWheelIgnoreSelection()
DisableControlAction(0, 140, true)
end
if not openMenu then
CreateThread(function()
while openMenu do
if not openMenu.disableInput then
DisablePlayerFiring(cache.playerId, true)
HudWeaponWheelIgnoreSelection()
DisableControlAction(0, 140, true)
end
Wait(0)
end
end)
end
Wait(0)
end
end)
end
openMenu = menu
keepInput = IsNuiFocusKeepingInput()
openMenu = menu
keepInput = IsNuiFocusKeepingInput()
if not menu.disableInput then
SetNuiFocusKeepInput(true)
end
if not menu.disableInput then
SetNuiFocusKeepInput(true)
end
SetNuiFocus(true, false)
SendNUIMessage({
action = 'setMenu',
data = {
position = menu.position,
canClose = menu.canClose,
title = menu.title,
items = menu.options,
startItemIndex = startIndex and startIndex - 1 or 0
}
})
SetNuiFocus(true, false)
SendNUIMessage({
action = 'setMenu',
data = {
position = menu.position,
canClose = menu.canClose,
title = menu.title,
items = menu.options,
startItemIndex = startIndex and startIndex - 1 or 0
}
})
end
local function resetFocus()
SetNuiFocus(false, false)
SetNuiFocusKeepInput(keepInput)
SetNuiFocus(false, false)
SetNuiFocusKeepInput(keepInput)
end
---@param onExit boolean?
function lib.hideMenu(onExit)
local menu = openMenu
openMenu = nil
local menu = openMenu
openMenu = nil
if onExit and menu.onClose then
menu.onClose()
end
if onExit and menu.onClose then
menu.onClose()
end
resetFocus()
SendNUIMessage({
action = 'closeMenu'
})
resetFocus()
SendNUIMessage({
action = 'closeMenu'
})
end
---@param id string
---@param options MenuOptions | MenuOptions[]
---@param index? number
function lib.setMenuOptions(id, options, index)
if index then
registeredMenus[id].options[index] = options
else
if not options[1] then return error('Invalid override format used, expected table of options.') end
registeredMenus[id].options = options
end
if index then
registeredMenus[id].options[index] = options
else
if not options[1] then error('Invalid override format used, expected table of options.') end
registeredMenus[id].options = options
end
end
---@return string?
function lib.getOpenMenu() return openMenu end
RegisterNUICallback('confirmSelected', function(data, cb)
cb(1)
data[1] += 1 -- selected
cb(1)
data[1] += 1 -- selected
if data[2] then
data[2] += 1 -- scrollIndex
end
if data[2] then
data[2] += 1 -- scrollIndex
end
local menu = openMenu
local menu = openMenu
if menu.options[data[1]].close ~= false then
resetFocus()
openMenu = nil
end
if menu.options[data[1]].close ~= false then
resetFocus()
openMenu = nil
end
if menu.cb then
menu.cb(data[1], data[2], menu.options[data[1]].args)
end
if menu.cb then
menu.cb(data[1], data[2], menu.options[data[1]].args)
end
end)
RegisterNUICallback('changeIndex', function(data, cb)
cb(1)
if not openMenu.onSideScroll then return end
cb(1)
if not openMenu.onSideScroll then return end
data[1] += 1 -- selected
data[1] += 1 -- selected
if data[2] then
data[2] += 1 -- scrollIndex
end
if data[2] then
data[2] += 1 -- scrollIndex
end
openMenu.onSideScroll(data[1], data[2], openMenu.options[data[1]].args)
openMenu.onSideScroll(data[1], data[2], openMenu.options[data[1]].args)
end)
RegisterNUICallback('changeSelected', function(data, cb)
cb(1)
if not openMenu.onSelected then return end
cb(1)
if not openMenu.onSelected then return end
data[1] += 1 -- selected
data[1] += 1 -- selected
if data[2] then
data[2] += 1 -- scrollIndex
end
if data[2] then
data[2] += 1 -- scrollIndex
end
openMenu.onSelected(data[1], data[2], openMenu.options[data[1]].args)
openMenu.onSelected(data[1], data[2], openMenu.options[data[1]].args)
end)
RegisterNUICallback('closeMenu', function(data, cb)
cb(1)
resetFocus()
cb(1)
resetFocus()
local menu = openMenu
openMenu = nil
local menu = openMenu
openMenu = nil
if menu.onClose then
menu.onClose()
end
end)
if menu.onClose then
menu.onClose()
end
end)

View File

@@ -1,32 +1,40 @@
--[[```lua
{
id?: string
title?: string
description: string
duration?: number
position?: 'top' | 'top-right' | 'top-left' | 'bottom' | 'bottom-right' | 'bottom-left'
style?: table
icon?: string
iconColor?: string
}
```]]
---@alias NotificationPosition 'top' | 'top-right' | 'top-left' | 'bottom' | 'bottom-right' | 'bottom-left'
---@alias NotificationType 'inform' | 'error' | 'success'
-- Custom notifications with a lot of allowed styling
---@param data table
---@class NotifyProps
---@field id? string
---@field title? string
---@field description? string
---@field duration? number
---@field position? NotificationPosition
---@field type? NotificationType
---@field style? string
---@field icon? string;
---@field iconColor? string;
---@param data NotifyProps
function lib.notify(data)
SendNUIMessage({
action = 'customNotify',
data = data
})
SendNUIMessage({
action = 'customNotify',
data = data
})
end
-- Default Chakra UI notifications
---@class DefaultNotifyProps
---@field title? string
---@field description? string
---@field duration? number
---@field position? NotificationPosition
---@field status? 'info' | 'warning' | 'success' | 'error'
---@field id? number
---@param data DefaultNotifyProps
function lib.defaultNotify(data)
SendNUIMessage({
action = 'notify',
data = data
})
SendNUIMessage({
action = 'notify',
data = data
})
end
RegisterNetEvent('ox_lib:notify', lib.notify)
RegisterNetEvent('ox_lib:defaultNotify', lib.defaultNotify)
RegisterNetEvent('ox_lib:defaultNotify', lib.defaultNotify)

View File

@@ -3,169 +3,193 @@ local DisableControlAction = DisableControlAction
local DisablePlayerFiring = DisablePlayerFiring
local playerState = LocalPlayer.state
---@class ProgressPropProps
---@field model string
---@field bone? number
---@field pos vector3
---@field rot vector3
---@class ProgressProps
---@field label? string
---@field duration number
---@field position? 'middle' | 'bottom'
---@field useWhileDead? boolean
---@field allowRagdoll? boolean
---@field allowCuffed? boolean
---@field allowFalling? boolean
---@field canCancel? boolean
---@field anim? { dict?: string, clip: string, flag?: number, blendIn?: number, blendOut?: number, duration?: number, playbackRate?: number, lockX?: boolean, lockY?: boolean, lockZ?: boolean, scenario?: string, playEnter?: boolean }
---@field prop? ProgressPropProps | ProgressPropProps[]
---@field disable? { move?: boolean, car?: boolean, combat?: boolean, mouse?: boolean }
local function createProp(prop)
lib.requestModel(prop.model)
local coords = GetEntityCoords(cache.ped)
local object = CreateObject(prop.model, coords.x, coords.y, coords.z, true, true, true)
AttachEntityToEntity(object, cache.ped, GetPedBoneIndex(cache.ped, prop.bone or 60309), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, 0, true)
return object
lib.requestModel(prop.model)
local coords = GetEntityCoords(cache.ped)
local object = CreateObject(prop.model, coords.x, coords.y, coords.z, true, true, true)
AttachEntityToEntity(object, cache.ped, GetPedBoneIndex(cache.ped, prop.bone or 60309), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, 0, true)
return object
end
local function interruptProgress(data)
if not data.useWhileDead and IsEntityDead(cache.ped) then return true end
if not data.allowRagdoll and IsPedRagdoll(cache.ped) then return true end
if not data.allowCuffed and IsPedCuffed(cache.ped) then return true end
if not data.allowFalling and IsPedFalling(cache.ped) then return true end
if not data.useWhileDead and IsEntityDead(cache.ped) then return true end
if not data.allowRagdoll and IsPedRagdoll(cache.ped) then return true end
if not data.allowCuffed and IsPedCuffed(cache.ped) then return true end
if not data.allowFalling and IsPedFalling(cache.ped) then return true end
end
local function startProgress(data)
playerState.invBusy = true
progress = data
playerState.invBusy = true
progress = data
if data.anim then
if data.anim.dict then
lib.requestAnimDict(data.anim.dict)
TaskPlayAnim(cache.ped, data.anim.dict, data.anim.clip, data.anim.blendIn or 3.0, data.anim.blendOut or 1.0, data.anim.duration or -1, data.anim.flag or 49, data.anim.playbackRate or 0, data.anim.lockX, data.anim.lockY, data.anim.lockZ)
data.anim = true
elseif data.anim.scenario then
TaskStartScenarioInPlace(cache.ped, data.anim.scenario, 0, data.anim.playEnter ~= nil and data.anim.playEnter or true)
data.anim = true
end
end
if data.anim then
if data.anim.dict then
lib.requestAnimDict(data.anim.dict)
TaskPlayAnim(cache.ped, data.anim.dict, data.anim.clip, data.anim.blendIn or 3.0, data.anim.blendOut or 1.0, data.anim.duration or -1, data.anim.flag or 49, data.anim.playbackRate or 0, data.anim.lockX, data.anim.lockY, data.anim.lockZ)
data.anim = true
elseif data.anim.scenario then
TaskStartScenarioInPlace(cache.ped, data.anim.scenario, 0, data.anim.playEnter ~= nil and data.anim.playEnter or true)
data.anim = true
end
end
if data.prop then
if data.prop.model then
data.prop1 = createProp(data.prop)
else
for i = 1, #data.prop do
local prop = data.prop[i]
if data.prop then
if data.prop.model then
data.prop1 = createProp(data.prop)
else
for i = 1, #data.prop do
local prop = data.prop[i]
if prop then
data['prop'..i] = createProp(prop)
end
end
end
end
if prop then
data['prop'..i] = createProp(prop)
end
end
end
end
local disable = data.disable
local disable = data.disable
while progress do
if disable then
if disable.mouse then
DisableControlAction(0, 1, true)
DisableControlAction(0, 2, true)
DisableControlAction(0, 106, true)
end
while progress do
if disable then
if disable.mouse then
DisableControlAction(0, 1, true)
DisableControlAction(0, 2, true)
DisableControlAction(0, 106, true)
end
if disable.move then
DisableControlAction(0, 21, true)
DisableControlAction(0, 30, true)
DisableControlAction(0, 31, true)
DisableControlAction(0, 36, true)
end
if disable.move then
DisableControlAction(0, 21, true)
DisableControlAction(0, 30, true)
DisableControlAction(0, 31, true)
DisableControlAction(0, 36, true)
end
if disable.car then
DisableControlAction(0, 63, true)
DisableControlAction(0, 64, true)
DisableControlAction(0, 71, true)
DisableControlAction(0, 72, true)
DisableControlAction(0, 75, true)
end
if disable.car then
DisableControlAction(0, 63, true)
DisableControlAction(0, 64, true)
DisableControlAction(0, 71, true)
DisableControlAction(0, 72, true)
DisableControlAction(0, 75, true)
end
if disable.combat then
DisableControlAction(0, 25, true)
DisablePlayerFiring(cache.playerId, true)
end
end
if disable.combat then
DisableControlAction(0, 25, true)
DisablePlayerFiring(cache.playerId, true)
end
end
if interruptProgress(progress) then
progress = false
end
if interruptProgress(progress) then
progress = false
end
Wait(0)
end
Wait(0)
end
if data.anim then
ClearPedTasks(cache.ped)
end
if data.anim then
ClearPedTasks(cache.ped)
end
if data.prop then
local n = #data.prop
for i = 1, n > 0 and n or 1 do
local prop = data['prop'..i]
if data.prop then
local n = #data.prop
for i = 1, n > 0 and n or 1 do
local prop = data['prop'..i]
if prop then
DeleteEntity(prop)
end
end
end
if prop then
DeleteEntity(prop)
end
end
end
playerState.invBusy = false
local cancel = progress == false
progress = nil
playerState.invBusy = false
local cancel = progress == false
progress = nil
if cancel then
SendNUIMessage({ action = 'progressCancel' })
return false
end
if cancel then
SendNUIMessage({ action = 'progressCancel' })
return false
end
return true
return true
end
---@param data ProgressProps
---@return boolean?
function lib.progressBar(data)
while progress ~= nil do Wait(100) end
while progress ~= nil do Wait(100) end
if not interruptProgress(data) then
SendNUIMessage({
action = 'progress',
data = {
label = data.label,
duration = data.duration
}
})
if not interruptProgress(data) then
SendNUIMessage({
action = 'progress',
data = {
label = data.label,
duration = data.duration
}
})
return startProgress(data)
end
return startProgress(data)
end
end
---@param data ProgressProps
---@return boolean?
function lib.progressCircle(data)
while progress ~= nil do Wait(100) end
while progress ~= nil do Wait(100) end
if not interruptProgress(data) then
SendNUIMessage({
action = 'circleProgress',
data = {
duration = data.duration,
position = data.position,
label = data.label
}
})
if not interruptProgress(data) then
SendNUIMessage({
action = 'circleProgress',
data = {
duration = data.duration,
position = data.position,
label = data.label
}
})
return startProgress(data)
end
return startProgress(data)
end
end
function lib.cancelProgress()
if not progress then
error('No progress bar is active')
elseif not progress.canCancel then
error('Progress bar cannot be cancelled')
end
if not progress then
error('No progress bar is active')
elseif not progress.canCancel then
error('Progress bar cannot be cancelled')
end
progress = false
progress = false
end
---@return boolean
function lib.progressActive()
return progress and true
return progress and true
end
RegisterNUICallback('progressComplete', function(data, cb)
cb(1)
progress = nil
cb(1)
progress = nil
end)
RegisterCommand('cancelprogress', function()
if progress?.canCancel then progress = false end
if progress?.canCancel then progress = false end
end)
RegisterKeyMapping('cancelprogress', 'Cancel current progress bar', 'keyboard', 'x')

View File

@@ -1,3 +1,11 @@
---@class TextUIOptions
---@field position? 'right-center' | 'left-center' | 'top-center';
---@field icon? string;
---@field iconColor? string;
---@field style? string;
---@param text string
---@param options TextUIOptions
function lib.showTextUI(text, options)
if not options then options = {} end
options.text = text
@@ -11,4 +19,4 @@ function lib.hideTextUI()
SendNUIMessage({
action = 'textUiHide'
})
end
end