From 6254ba9ba2df07a090fc808ca220212736f4ead2 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sun, 18 Sep 2022 09:06:32 +1000 Subject: [PATCH] refactor(resource/interface): formatting and type definitions --- resource/interface/client/alert.lua | 10 +- resource/interface/client/clipboard.lua | 11 +- resource/interface/client/context.lua | 36 ++- resource/interface/client/input.lua | 64 +++-- resource/interface/client/menu.lua | 220 +++++++++-------- resource/interface/client/notify.lua | 56 +++-- resource/interface/client/progress.lua | 298 +++++++++++++----------- resource/interface/client/textui.lua | 10 +- 8 files changed, 414 insertions(+), 291 deletions(-) diff --git a/resource/interface/client/alert.lua b/resource/interface/client/alert.lua index b598f0afd..230ccf30d 100644 --- a/resource/interface/client/alert.lua +++ b/resource/interface/client/alert.lua @@ -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) \ No newline at end of file diff --git a/resource/interface/client/clipboard.lua b/resource/interface/client/clipboard.lua index ee5a7fb11..984c319ec 100644 --- a/resource/interface/client/clipboard.lua +++ b/resource/interface/client/clipboard.lua @@ -1,6 +1,7 @@ +---@param value string function lib.setClipboard(value) - SendNUIMessage({ - action = 'setClipboard', - data = value - }) -end + SendNUIMessage({ + action = 'setClipboard', + data = value + }) +end \ No newline at end of file diff --git a/resource/interface/client/context.lua b/resource/interface/client/context.lua index e80015776..1d284a4e8 100644 --- a/resource/interface/client/context.lua +++ b/resource/interface/client/context.lua @@ -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) \ No newline at end of file diff --git a/resource/interface/client/input.lua b/resource/interface/client/input.lua index 63152b31e..8e3bb97fb 100644 --- a/resource/interface/client/input.lua +++ b/resource/interface/client/input.lua @@ -1,33 +1,49 @@ local input -function lib.inputDialog(heading, rows) - if input then return end - input = promise.new() +---@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 - -- Backwards compat with string tables - for i = 1, #rows do - if type(rows[i]) == 'string' then - rows[i] = {type = 'input', label = rows[i]} - end +---@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() - 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 - 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) \ No newline at end of file diff --git a/resource/interface/client/menu.lua b/resource/interface/client/menu.lua index 53391142b..0b6e34616 100644 --- a/resource/interface/client/menu.lua +++ b/resource/interface/client/menu.lua @@ -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] - - if not menu then - return 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 - - Wait(0) - end - end) - end - - openMenu = menu - keepInput = IsNuiFocusKeepingInput() - - 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 - } - }) + local menu = registeredMenus[id] + + 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 + + Wait(0) + end + end) + end + + openMenu = menu + keepInput = IsNuiFocusKeepingInput() + + 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 + } + }) 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) \ No newline at end of file diff --git a/resource/interface/client/notify.lua b/resource/interface/client/notify.lua index 2b3c8067b..214c69d62 100644 --- a/resource/interface/client/notify.lua +++ b/resource/interface/client/notify.lua @@ -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) \ No newline at end of file diff --git a/resource/interface/client/progress.lua b/resource/interface/client/progress.lua index 7141a78cf..1eddd3f47 100644 --- a/resource/interface/client/progress.lua +++ b/resource/interface/client/progress.lua @@ -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 - - 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 prop then - data['prop'..i] = createProp(prop) - end - end - end - end - - 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 - - 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.combat then - DisableControlAction(0, 25, true) - DisablePlayerFiring(cache.playerId, true) - end - end - - if interruptProgress(progress) then - progress = false - end - - Wait(0) - 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 prop then - DeleteEntity(prop) - end - end - end - - playerState.invBusy = false - local cancel = progress == false - progress = nil - - if cancel then - SendNUIMessage({ action = 'progressCancel' }) - return false - end - - return true + 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.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 + + 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 + + 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.combat then + DisableControlAction(0, 25, true) + DisablePlayerFiring(cache.playerId, true) + end + end + + if interruptProgress(progress) then + progress = false + end + + Wait(0) + 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 prop then + DeleteEntity(prop) + end + end + end + + playerState.invBusy = false + local cancel = progress == false + progress = nil + + if cancel then + SendNUIMessage({ action = 'progressCancel' }) + return false + end + + return true end +---@param data ProgressProps +---@return boolean? function lib.progressBar(data) - while progress ~= nil do Wait(100) end - - if not interruptProgress(data) then - SendNUIMessage({ - action = 'progress', - data = { - label = data.label, - duration = data.duration - } - }) - - return startProgress(data) - end + while progress ~= nil do Wait(100) end + + if not interruptProgress(data) then + SendNUIMessage({ + action = 'progress', + data = { + label = data.label, + duration = data.duration + } + }) + + return startProgress(data) + end end +---@param data ProgressProps +---@return boolean? function lib.progressCircle(data) - 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 - } - }) - - return startProgress(data) - 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 + } + }) + + 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') diff --git a/resource/interface/client/textui.lua b/resource/interface/client/textui.lua index 56bd3e2ac..2f67018ab 100644 --- a/resource/interface/client/textui.lua +++ b/resource/interface/client/textui.lua @@ -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 \ No newline at end of file