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 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) function lib.alertDialog(data)
if alert then return end if alert then return end
alert = promise.new() alert = promise.new()
@@ -21,4 +29,4 @@ RegisterNUICallback('closeAlert', function(data, cb)
promise:resolve(data) promise:resolve(data)
end) 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) function lib.setClipboard(value)
SendNUIMessage({ SendNUIMessage({
action = 'setClipboard', action = 'setClipboard',
data = value data = value
}) })
end end

View File

@@ -1,16 +1,41 @@
local contextMenus = {} local contextMenus = {}
local openContextMenu = nil 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) local function closeContext(_, cb, onExit)
if cb then cb(1) end if cb then cb(1) end
if (cb or onExit) and contextMenus[openContextMenu].onExit then contextMenus[openContextMenu].onExit() end if (cb or onExit) and contextMenus[openContextMenu].onExit then contextMenus[openContextMenu].onExit() end
SetNuiFocus(false, false) SetNuiFocus(false, false)
if not cb then SendNUIMessage({action = 'hideContext'}) end if not cb then SendNUIMessage({ action = 'hideContext' }) end
openContextMenu = nil openContextMenu = nil
end end
---@param id string
function lib.showContext(id) 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] local data = contextMenus[id]
openContextMenu = id openContextMenu = id
SetNuiFocus(true, true) SetNuiFocus(true, true)
@@ -25,6 +50,7 @@ function lib.showContext(id)
}, { sort_keys = true })) }, { sort_keys = true }))
end end
---@param context ContextMenuProps | ContextMenuProps[]
function lib.registerContext(context) function lib.registerContext(context)
for k, v in pairs(context) do for k, v in pairs(context) do
if type(k) == 'number' then if type(k) == 'number' then
@@ -36,9 +62,11 @@ function lib.registerContext(context)
end end
end end
---@return string?
function lib.getOpenContextMenu() return openContextMenu end 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) RegisterNUICallback('openContext', function(id, cb)
cb(1) cb(1)
@@ -64,4 +92,4 @@ RegisterNUICallback('clickContext', function(id, cb)
}) })
end) end)
RegisterNUICallback('closeContext', closeContext) RegisterNUICallback('closeContext', closeContext)

View File

@@ -1,33 +1,49 @@
local input 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) function lib.inputDialog(heading, rows)
if input then return end if input then return end
input = promise.new() input = promise.new()
-- Backwards compat with string tables -- Backwards compat with string tables
for i = 1, #rows do for i = 1, #rows do
if type(rows[i]) == 'string' then if type(rows[i]) == 'string' then
rows[i] = {type = 'input', label = rows[i]} rows[i] = { type = 'input', label = rows[i] --[[@as string]] }
end end
end
end SetNuiFocus(true, true)
SendNUIMessage({
action = 'openDialog',
data = {
heading = heading,
rows = rows
}
})
SetNuiFocus(true, true) return Citizen.Await(input)
SendNUIMessage({
action = 'openDialog',
data = {
heading = heading,
rows = rows
}
})
return Citizen.Await(input)
end end
RegisterNUICallback('inputData', function(data, cb) RegisterNUICallback('inputData', function(data, cb)
cb(1) cb(1)
SetNuiFocus(false, false) SetNuiFocus(false, false)
local promise = input local promise = input
input = nil input = nil
promise:resolve(data) promise:resolve(data)
end) end)

View File

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

View File

@@ -1,32 +1,40 @@
--[[```lua ---@alias NotificationPosition 'top' | 'top-right' | 'top-left' | 'bottom' | 'bottom-right' | 'bottom-left'
{ ---@alias NotificationType 'inform' | 'error' | 'success'
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
}
```]]
-- Custom notifications with a lot of allowed styling ---@class NotifyProps
---@param data table ---@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) function lib.notify(data)
SendNUIMessage({ SendNUIMessage({
action = 'customNotify', action = 'customNotify',
data = data data = data
}) })
end 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) function lib.defaultNotify(data)
SendNUIMessage({ SendNUIMessage({
action = 'notify', action = 'notify',
data = data data = data
}) })
end end
RegisterNetEvent('ox_lib:notify', lib.notify) 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 DisablePlayerFiring = DisablePlayerFiring
local playerState = LocalPlayer.state 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) local function createProp(prop)
lib.requestModel(prop.model) lib.requestModel(prop.model)
local coords = GetEntityCoords(cache.ped) local coords = GetEntityCoords(cache.ped)
local object = CreateObject(prop.model, coords.x, coords.y, coords.z, true, true, true) 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) 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 return object
end end
local function interruptProgress(data) local function interruptProgress(data)
if not data.useWhileDead and IsEntityDead(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.allowRagdoll and IsPedRagdoll(cache.ped) then return true end
if not data.allowCuffed and IsPedCuffed(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.allowFalling and IsPedFalling(cache.ped) then return true end
end end
local function startProgress(data) local function startProgress(data)
playerState.invBusy = true playerState.invBusy = true
progress = data progress = data
if data.anim then if data.anim then
if data.anim.dict then if data.anim.dict then
lib.requestAnimDict(data.anim.dict) 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) 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 data.anim = true
elseif data.anim.scenario then elseif data.anim.scenario then
TaskStartScenarioInPlace(cache.ped, data.anim.scenario, 0, data.anim.playEnter ~= nil and data.anim.playEnter or true) TaskStartScenarioInPlace(cache.ped, data.anim.scenario, 0, data.anim.playEnter ~= nil and data.anim.playEnter or true)
data.anim = true data.anim = true
end end
end end
if data.prop then if data.prop then
if data.prop.model then if data.prop.model then
data.prop1 = createProp(data.prop) data.prop1 = createProp(data.prop)
else else
for i = 1, #data.prop do for i = 1, #data.prop do
local prop = data.prop[i] local prop = data.prop[i]
if prop then if prop then
data['prop'..i] = createProp(prop) data['prop'..i] = createProp(prop)
end end
end end
end end
end end
local disable = data.disable local disable = data.disable
while progress do while progress do
if disable then if disable then
if disable.mouse then if disable.mouse then
DisableControlAction(0, 1, true) DisableControlAction(0, 1, true)
DisableControlAction(0, 2, true) DisableControlAction(0, 2, true)
DisableControlAction(0, 106, true) DisableControlAction(0, 106, true)
end end
if disable.move then if disable.move then
DisableControlAction(0, 21, true) DisableControlAction(0, 21, true)
DisableControlAction(0, 30, true) DisableControlAction(0, 30, true)
DisableControlAction(0, 31, true) DisableControlAction(0, 31, true)
DisableControlAction(0, 36, true) DisableControlAction(0, 36, true)
end end
if disable.car then if disable.car then
DisableControlAction(0, 63, true) DisableControlAction(0, 63, true)
DisableControlAction(0, 64, true) DisableControlAction(0, 64, true)
DisableControlAction(0, 71, true) DisableControlAction(0, 71, true)
DisableControlAction(0, 72, true) DisableControlAction(0, 72, true)
DisableControlAction(0, 75, true) DisableControlAction(0, 75, true)
end end
if disable.combat then if disable.combat then
DisableControlAction(0, 25, true) DisableControlAction(0, 25, true)
DisablePlayerFiring(cache.playerId, true) DisablePlayerFiring(cache.playerId, true)
end end
end end
if interruptProgress(progress) then if interruptProgress(progress) then
progress = false progress = false
end end
Wait(0) Wait(0)
end end
if data.anim then if data.anim then
ClearPedTasks(cache.ped) ClearPedTasks(cache.ped)
end end
if data.prop then if data.prop then
local n = #data.prop local n = #data.prop
for i = 1, n > 0 and n or 1 do for i = 1, n > 0 and n or 1 do
local prop = data['prop'..i] local prop = data['prop'..i]
if prop then if prop then
DeleteEntity(prop) DeleteEntity(prop)
end end
end end
end end
playerState.invBusy = false playerState.invBusy = false
local cancel = progress == false local cancel = progress == false
progress = nil progress = nil
if cancel then if cancel then
SendNUIMessage({ action = 'progressCancel' }) SendNUIMessage({ action = 'progressCancel' })
return false return false
end end
return true return true
end end
---@param data ProgressProps
---@return boolean?
function lib.progressBar(data) function lib.progressBar(data)
while progress ~= nil do Wait(100) end while progress ~= nil do Wait(100) end
if not interruptProgress(data) then if not interruptProgress(data) then
SendNUIMessage({ SendNUIMessage({
action = 'progress', action = 'progress',
data = { data = {
label = data.label, label = data.label,
duration = data.duration duration = data.duration
} }
}) })
return startProgress(data) return startProgress(data)
end end
end end
---@param data ProgressProps
---@return boolean?
function lib.progressCircle(data) function lib.progressCircle(data)
while progress ~= nil do Wait(100) end while progress ~= nil do Wait(100) end
if not interruptProgress(data) then if not interruptProgress(data) then
SendNUIMessage({ SendNUIMessage({
action = 'circleProgress', action = 'circleProgress',
data = { data = {
duration = data.duration, duration = data.duration,
position = data.position, position = data.position,
label = data.label label = data.label
} }
}) })
return startProgress(data) return startProgress(data)
end end
end end
function lib.cancelProgress() function lib.cancelProgress()
if not progress then if not progress then
error('No progress bar is active') error('No progress bar is active')
elseif not progress.canCancel then elseif not progress.canCancel then
error('Progress bar cannot be cancelled') error('Progress bar cannot be cancelled')
end end
progress = false progress = false
end end
---@return boolean
function lib.progressActive() function lib.progressActive()
return progress and true return progress and true
end end
RegisterNUICallback('progressComplete', function(data, cb) RegisterNUICallback('progressComplete', function(data, cb)
cb(1) cb(1)
progress = nil progress = nil
end) end)
RegisterCommand('cancelprogress', function() RegisterCommand('cancelprogress', function()
if progress?.canCancel then progress = false end if progress?.canCancel then progress = false end
end) end)
RegisterKeyMapping('cancelprogress', 'Cancel current progress bar', 'keyboard', 'x') 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) function lib.showTextUI(text, options)
if not options then options = {} end if not options then options = {} end
options.text = text options.text = text
@@ -11,4 +19,4 @@ function lib.hideTextUI()
SendNUIMessage({ SendNUIMessage({
action = 'textUiHide' action = 'textUiHide'
}) })
end end