Files
GuildRecipes_Octo/TradeskillRecipesGui.lua
T
2026-07-15 23:00:59 -07:00

760 lines
29 KiB
Lua

GuildRecipesOcto = GuildRecipesOcto or {}
---@class GuildRecipesOcto
local m = GuildRecipesOcto
if m.Tradeskills then return end
---@class TradeskillGui
---@field show fun( tab: string? )
---@field hide fun()
---@field toggle fun()
---@field is_visible fun(): boolean
---@field update fun()
local M = {}
local _G = getfenv()
---@return TradeskillGui
---@nodiscard
function M.new()
local popup
local selected_tradeskill = "All tradeskills"
local selected
local offset = 0
local frame_items = {}
local search_result = {}
local function save_position( self )
local point, _, relative_point, x, y = self:GetPoint()
m.db.frame_tradeskills.position = {
point = point,
relative_point = relative_point,
x = x,
y = y
}
end
local function refresh()
for i = 1, 12 do
if search_result[ i + offset ] then
frame_items[ i ].set_item( search_result[ i + offset ] )
frame_items[ i ].set_selected( selected == i + offset )
else
frame_items[ i ]:Hide()
end
end
local max = math.max( 0, getn( search_result ) - 12 )
local value = math.min( max, popup.scroll_bar:GetValue() )
if value == 0 then
_G[ "GuildTradeskillsOctoScrollBarScrollUpButton" ]:Disable()
else
_G[ "GuildTradeskillsOctoScrollBarScrollUpButton" ]:Enable()
end
if value == max then
_G[ "GuildTradeskillsOctoScrollBarScrollDownButton" ]:Disable()
else
_G[ "GuildTradeskillsOctoScrollBarScrollDownButton" ]:Enable()
end
end
local function initialize_dropdown_skill()
local info = {}
info.text = "All tradeskills"
info.value = info.text
info.arg1 = info.text
info.notCheckable = true
info.func = function( value )
UIDropDownMenu_SetText( value, popup.dropdown_skill )
selected_tradeskill = value
end
UIDropDownMenu_AddButton( info )
for key, opt in pairs( m.TRADE_SKILL_LOCALIZATION ) do
info.text = opt.enUS
info.value = key
info.arg1 = key
UIDropDownMenu_AddButton( info )
end
end
local function do_search( search_str )
search_result = {}
local function search( skill )
if m.db.tradeskills[ skill ] then
for id, item in pairs( m.db.tradeskills[ skill ] ) do
if item.n and string.find( string.upper( item.n ), string.upper( search_str ), nil, true ) then
table.insert( search_result, {
id = id,
name = item.n,
players = item.p,
quality = item.q,
skill = skill
} )
end
end
end
end
if not selected_tradeskill or selected_tradeskill == "All tradeskills" then
for key in pairs( m.db.tradeskills ) do
search( key )
end
else
search( selected_tradeskill )
end
table.sort( search_result, function( a, b )
return a.name < b.name
end )
local max = math.max( 0, getn( search_result ) - 12 )
popup.scroll_bar:SetMinMaxValues( 0, max )
popup.scroll_bar:SetValue( 0 )
refresh()
end
local function show_recipe( item, index )
if selected == index then
popup.crafters.clear()
popup.info.clear()
selected = nil
return
else
popup.crafters.set( item )
selected = index
end
if item.skill == "Enchanting" then
item.link = m.make_enchant_link( item.id, item.name )
popup.info.set( item )
refresh()
return
end
if GetSpellInfoAtlasLootDB then
local recipe = m.find( item.id, GetSpellInfoAtlasLootDB[ "craftspells" ], "craftItem" )
if recipe then
recipe.link = m.make_item_link( item.id, item.name, item.quality )
popup.info.set( recipe )
else
popup.info.clear( item.name .. " was not found in AtlasLoot database." )
end
else
popup.info.clear( "AtlasLoot is required to view recipes." )
end
refresh()
end
local function create_item( parent, index )
local frame = m.FrameBuilder.new()
:type( "Button" )
:parent( parent )
:width( 382 )
:height( 16 )
:frame_style( "NONE" )
:build()
frame.slot_index = index
frame:Hide()
frame:SetHighlightTexture( "Interface\\QuestFrame\\UI-QuestTitleHighlight" )
frame:SetScript( "OnMouseUp", function()
show_recipe( frame.item, frame.slot_index + offset )
end )
local selected_tex = frame:CreateTexture( nil, "BACKGROUND" )
selected_tex:SetTexture( "Interface\\QuestFrame\\UI-QuestLogTitleHighlight" )
selected_tex:SetAllPoints( frame )
selected_tex:SetVertexColor( 0.3, 0.3, 1, 1 )
selected_tex:Hide()
local text_item = m.GuiElements.create_text_in_container( frame, nil, "Button" )
text_item:SetPoint( "Left", frame, "Left", 5, 0 )
text_item:SetHeight( 16 )
text_item:EnableMouse( true )
text_item:SetScript( "OnEnter", function()
GameTooltip:SetOwner( this, "ANCHOR_RIGHT" )
GameTooltip:SetHyperlink( string.format( "%s:%d", frame.item.skill == "Enchanting" and "enchant" or "item", frame.item.id ) )
frame:LockHighlight()
end )
text_item:SetScript( "OnLeave", function()
GameTooltip:Hide()
frame:UnlockHighlight()
end )
text_item:SetScript( "OnClick", function()
if IsShiftKeyDown() and frame.item then
if ChatFrameEditBox:IsVisible() then
if frame.item.skill == "Enchanting" then
ChatFrameEditBox:Insert( m.make_enchant_link( frame.item.id, frame.item.name ) )
else
ChatFrameEditBox:Insert( m.make_item_link( frame.item.id, frame.item.name, frame.item.quality ) )
end
return
end
end
show_recipe( frame.item, frame.slot_index + offset )
end )
local text_tradeskill = frame:CreateFontString( nil, "ARTWORK", "GRFontNormal" )
text_tradeskill:SetPoint( "Right", frame, "Right", -5, 0 )
text_tradeskill:SetHeight( 16 )
text_tradeskill:SetJustifyH( "Right" )
frame.set_selected = function( select )
if select then
selected_tex:Show()
else
selected_tex:Hide()
end
end
frame.set_item = function( item )
frame.item = item
if item.skill == "Enchanting" then
text_item:SetText( string.format( "|cFF%s%s|r", "80B0FF", item.name ) )
else
text_item:SetText( m.get_item_name_colorized( item ) )
end
if not selected_tradeskill or selected_tradeskill == "All tradeskills" then
text_tradeskill:SetText( item.skill )
else
text_tradeskill:SetText( "" )
end
frame:Show()
end
return frame
end
local function create_reagent( parent, index )
local frame = CreateFrame( "Button", "GuildTradeskillsOctoReagent" .. index, parent, "QuestItemTemplate" )
local x = mod( index, 3 ) == 0 and 320 or (mod( index, 3 ) - 1) * 160
local y = -5 - ((math.ceil( index / 3 ) - 1) * 52)
frame:SetScale( 0.8 )
frame:SetPoint( "TopLeft", parent.label_reagents, "BottomLeft", x, y )
frame:Hide()
frame.reagent_id = nil
frame:SetScript( "OnEnter", function()
if frame.reagent_id then
GameTooltip:SetOwner( this, "ANCHOR_LEFT" )
GameTooltip:SetHyperlink( string.format( "item:%d", frame.reagent_id ) )
end
end )
frame:SetScript( "OnLeave", function()
GameTooltip:Hide()
end )
return frame
end
local function create_crafters_frame( parent )
local frame = m.FrameBuilder.new()
:parent( parent )
:point( "TopLeft", parent.border_results, "BottomLeft", 0, -5 )
:point( "Right", parent.btn_search, "Right", 0, 0 )
:height( 70 )
:frame_style( "TOOLTIP" )
:backdrop( { bgFile = "Interface/Buttons/WHITE8x8" } )
:backdrop_color( 0, 0, 0, 1 )
:build()
local label_crafters = frame:CreateFontString( nil, "ARTWORK", "GRFontHighlight" )
label_crafters:SetPoint( "TopLeft", frame, "TopLeft", 10, -10 )
local text_crafters = frame:CreateFontString( nil, "ARTWORK", "GRFontHighlight" )
text_crafters:SetPoint( "TopLeft", frame, "TopLeft", 10, -25 )
text_crafters:SetWidth( 300 )
text_crafters:SetJustifyH( "Left" )
local btn_reagents = m.GuiElements.create_button( frame, "Show reagents", 80, function()
if this:GetText() == "Show reagents" then
m.db.frame_tradeskills.show_reagents = true
this:SetText( "Hide reagents" )
parent.info:Show()
parent:SetHeight( 584 )
else
m.db.frame_tradeskills.show_reagents = false
this:SetText( "Show reagents" )
parent.info:Hide()
parent:SetHeight( 354 )
end
end )
btn_reagents:SetPoint( "BottomRight", frame, "BottomRight", -8, 10 )
btn_reagents:Hide()
frame.btn_reagents = btn_reagents
frame.clear = function()
label_crafters:SetText( "" )
text_crafters:SetText( "" )
btn_reagents:Hide()
end
frame.set = function( item )
local have_alts = GuildAlts and GuildAlts.version and true or false
local online_roster = m.build_online_roster()
local players = ""
for player_id in string.gmatch(item.players, "([^,]+)") do
local player = m.db.players[ tonumber(player_id) ]
local color = online_roster[ player ] and "FFFFFF" or "AAAAAA"
local main
if have_alts then
main = GuildAlts.get_main( player )
if main then
local main_color = online_roster[ main ] and "FFFFFF" or "AAAAAA"
main = string.format( "|cFF%s(%s)|r", main_color, main )
end
end
players = players .. string.format( "|cFF%s%s|r%s, ", color, player, main or "" )
end
label_crafters:SetText( string.format( "%s is craftable by:", m.get_item_name_colorized( item ) ) )
text_crafters:SetText( string.match( players, "(.-), $" ) )
btn_reagents:Show()
end
return frame
end
local function create_info_frame( parent )
local frame = m.FrameBuilder.new()
:parent( parent )
:point( "TopLeft", parent.crafters, "BottomLeft", 0, -5 )
:point( "Right", parent.btn_search, "Right", 0, 0 )
:height( 224 )
:frame_style( "TOOLTIP" )
:backdrop( { bgFile = "Interface/Buttons/WHITE8x8" } )
:backdrop_color( 0, 0, 0, 1 )
:build()
local info = CreateFrame( "Frame", nil, frame )
info:SetWidth( 380 )
info:SetHeight( 1 )
frame.info = info
local scroll_frame = CreateFrame( "ScrollFrame", "GuildTradeskillsOctoInfoScrollFrame", frame, "UIPanelScrollFrameTemplate" )
scroll_frame:SetPoint( "TopLeft", frame, "TopLeft", 5, -5 )
scroll_frame:SetPoint( "BottomRight", frame, "BottomRight", -20, 5 )
_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBar" ]:ClearAllPoints()
_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBar" ]:SetPoint( "TopLeft", scroll_frame, "TopRight", 0, -16 )
_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBar" ]:SetPoint( "Bottom", scroll_frame, "Bottom", 0, 15 )
scroll_frame:SetScrollChild( info )
local frame_icon = CreateFrame( "Button", nil, info )
frame_icon:SetPoint( "TopLeft", info, "TopLeft", 5, -5 )
frame_icon:SetWidth( 32 )
frame_icon:SetHeight( 32 )
frame_icon:SetScript( "OnClick", function()
if IsShiftKeyDown() then
if ChatFrameEditBox:IsVisible() then
local chat_type = ChatFrameEditBox.chatType
local recipe = frame.recipe
SendChatMessage( string.format( "Crafting of %s requires the following reagents:", recipe.link ), chat_type )
for _, reagent in pairs( recipe.reagent_data ) do
SendChatMessage( string.format( "%s (%d)", reagent.link, reagent.count ), chat_type )
end
return
end
end
end )
local icon = info:CreateTexture( nil, "ARTWORK" )
icon:SetAllPoints( frame_icon )
local text_name = info:CreateFontString( nil, "ARTWORK", "GRFontNormal" )
text_name:SetPoint( "TopLeft", info, "TopLeft", 45, -5 )
text_name:SetJustifyH( "Left" )
local text_stats = info:CreateFontString( nil, "ARTWORK", "GRFontHighlightSmall" )
text_stats:SetPoint( "TopLeft", text_name, "BottomLeft", 0, 0 )
text_stats:SetJustifyH( "Left" )
local text_info = info:CreateFontString( nil, "ARTWORK", "GRFontHighlightSmall" )
text_info:SetPoint( "TopLeft", text_stats, "BottomLeft", 0, 0 )
text_info:SetWidth( 330 )
text_info:SetJustifyH( "Left" )
text_info:SetTextColor( 0, 1, 0, 1 )
local label_reagents = frame:CreateFontString( nil, "ARTWORK", "GRFontHighlightSmall" )
label_reagents:SetPoint( "Top", text_info, "Bottom", 0, -10 )
label_reagents:SetPoint( "Left", info, "Left", 5, 0 )
label_reagents:SetText( "Reagents:" )
info.label_reagents = label_reagents
for i = 1, 8 do
create_reagent( info, i )
end
frame.clear = function( text )
frame.recipe = nil
icon:SetTexture( nil )
text_name:SetText( "" )
text_stats:SetText( "" )
text_info:SetText( "" )
label_reagents:SetText( text or "" )
for i = 1, 8 do
local reagent = getglobal( "GuildTradeskillsOctoReagent" .. i )
reagent:Hide()
end
scroll_frame:UpdateScrollChildRect()
if ((_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBarScrollUpButton" ]:IsEnabled() == 0) and (_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBarScrollDownButton" ]:IsEnabled() == 0)) then
_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBar" ]:Hide()
else
_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBar" ]:Show()
end
end
local function set_reagent( slot, reagent_id, reagent_name, reagent_texture, reagent_count )
local reagent = getglobal( "GuildTradeskillsOctoReagent" .. slot )
local f_name = getglobal( "GuildTradeskillsOctoReagent" .. slot .. "Name" )
local f_count = getglobal( "GuildTradeskillsOctoReagent" .. slot .. "Count" )
local player_reagent_count = m.find_item_count_bag( 0, 4, reagent_name )
reagent.reagent_id = reagent_id
if not reagent_texture or not reagent_name then
m.get_item_info( reagent_id, function( item_info )
f_name:SetText( item_info.name )
SetItemButtonTexture( reagent, item_info.texture )
end )
else
f_name:SetText( reagent_name )
SetItemButtonTexture( reagent, reagent_texture )
end
if (player_reagent_count < reagent_count) then
SetItemButtonTextureVertexColor( reagent, GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b );
f_name:SetTextColor( GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b );
else
SetItemButtonTextureVertexColor( reagent, 1.0, 1.0, 1.0 );
f_name:SetTextColor( HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b );
end
f_count:SetText( player_reagent_count .. " /" .. reagent_count )
reagent:Show()
end
frame.refresh = function()
if frame.recipe then
frame.set( frame.recipe )
end
end
frame.set = function( recipe )
if not recipe then return end
recipe.reagent_data = {}
frame.recipe = recipe
for i = 1, 8 do
local reagent = getglobal( "GuildTradeskillsOctoReagent" .. i )
reagent:Hide()
end
if recipe.skill == "Enchanting" then
icon:SetTexture( m.Enchants[ recipe.id ].icon )
m.scan_tooltip( string.format( "enchant:%d", recipe.id ), function( lines )
local stats = ""
local desc = ""
local reagents = {}
if lines then
for i, line in ipairs( lines ) do
if i > 1 then
if string.find( line, "Reagents:" ) then
line = string.gsub( line, "Reagents: ", "" )
for reagent in string.gmatch( line, "([^,]+)" ) do
local name, count = string.match( reagent, "^%s?(.-)%s*%((%d+)%)" )
local texture, link, quality
name = m.clean_string( name and name or reagent )
if m.Reagents[ name ] then
_, link, quality, _, _, _, _, _, texture = GetItemInfo( m.Reagents[ name ] )
else
texture = "Interface\\Icons\\INV_Misc_QuestionMark"
end
table.insert( reagents, {
id = m.Reagents[ name ],
name = name,
link = m.Reagents[ name ] and m.make_item_link( m.Reagents[ name ], name, quality ) or name,
count = tonumber( count ) or 1,
icon = texture
} )
end
else
if getn( reagents ) > 0 then
desc = desc .. line .. "\n"
else
stats = stats .. line .. "\n"
end
end
end
end
end
text_name:SetText( recipe.name )
text_stats:SetText( stats ~= "" and stats or "\n\n" )
text_info:SetText( desc )
label_reagents:SetText( "Reagents:" )
for i, reagent_data in pairs( reagents ) do
if not frame.recipe or not frame.recipe.reagent_data then return end
set_reagent( i, reagent_data.id, reagent_data.name, reagent_data.icon, reagent_data.count )
frame.recipe.reagent_data[ i ] = reagent_data
end
end )
else
m.get_item_info( recipe.craftItem, function( item_info )
local item = {
name = item_info.name,
id = recipe.craftItem,
quality = item_info.quality,
icon = item_info.texture,
data = {}
}
m.tooltip:ClearLines()
m.tooltip:SetHyperlink( "item:" .. item.id )
m.tooltip:Show()
local num_lines = m.tooltip:NumLines()
local stats = ""
local desc = ""
for i = 1, num_lines do
local line = _G[ "GuildRecipesOctoTooltipTextLeft" .. i ]:GetText()
if string.find( line, "^%s*$" ) then
break
end
if string.find( line, "Use:" ) or string.find( line, "Equip:" ) then
desc = desc .. line .. "\n"
elseif i > 1 and desc == "" then
stats = stats .. line .. "\n"
end
end
icon:SetTexture( item.icon )
text_name:SetText( m.get_item_name_colorized( item ) )
text_stats:SetText( stats ~= "" and stats or "\n\n" )
text_info:SetText( desc )
label_reagents:SetText( "Reagents:" )
for i, reagent_data in pairs( recipe.reagents ) do
local reagent_count = reagent_data[ 2 ] or 1
m.get_item_info( reagent_data[ 1 ], function( reagent_info, data )
if not frame.recipe or not frame.recipe.reagent_data then return end
set_reagent( data.i, reagent_info.id, reagent_info.name, reagent_info.texture, reagent_count )
frame.recipe.reagent_data[ data.i ] = {
id = reagent_info.id,
name = reagent_info.name,
link = m.make_item_link( reagent_info.id, reagent_info.name, reagent_info.quality ),
count = reagent_count
}
end, { i = i } )
end
end )
end
scroll_frame:UpdateScrollChildRect()
if ((_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBarScrollUpButton" ]:IsEnabled() == 0) and (_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBarScrollDownButton" ]:IsEnabled() == 0)) then
_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBar" ]:Hide()
else
_G[ "GuildTradeskillsOctoInfoScrollFrameScrollBar" ]:Show()
end
end
frame.clear()
return frame
end
local function create_frame()
local frame = m.FrameBuilder.new()
:name( "GuildRecipesOctoFrame" )
:title( string.format( "Guild Recipes Octo v%s", m.version ) )
:frame_style( "TOOLTIP" )
:frame_level( 100 )
:backdrop( { bgFile = "Interface/Buttons/WHITE8x8" } )
:backdrop_color( 0, 0, 0, 0.9 )
:close_button()
:width( 427 )
:height( 354 )
:movable()
:esc()
:hidden()
:on_drag_stop( save_position )
:build()
if m.db.frame_tradeskills.position then
local p = m.db.frame_tradeskills.position
frame:ClearAllPoints()
frame:SetPoint( p.point, UIParent, p.relative_point, p.x, p.y )
end
local label_search = frame:CreateFontString( nil, "ARTWORK", "GRFontNormal" )
label_search:SetPoint( "TopLeft", frame, "TopLeft", 12, -35 )
label_search:SetTextColor( 1, 1, 1 )
label_search:SetJustifyH( "Left" )
label_search:SetText( "Search" )
local input_search = CreateFrame( "EditBox", "GuildTradeskillsOctoInputSearch", frame, "InputBoxTemplate" )
frame.search = input_search
input_search:SetPoint( "TopLeft", frame, "TopLeft", 60, -29 )
input_search:SetWidth( 180 )
input_search:SetHeight( 22 )
input_search:SetAutoFocus( false )
input_search:SetScript( "OnEscapePressed", function()
input_search:ClearFocus()
end )
input_search:SetScript( "OnEnterPressed", function()
do_search( input_search:GetText() )
end )
local dropdown_skill = CreateFrame( "Frame", "GuildTradeskillsOctoSkillDropdown", frame, "UIDropDownMenuTemplate" )
frame.dropdown_skill = dropdown_skill
dropdown_skill:SetPoint( "TopLeft", input_search, "TopRight", -5, 1 )
dropdown_skill:SetScale( 0.9 )
local btn_search = m.GuiElements.create_button( frame, "Search", 60, function()
do_search( input_search:GetText() )
end )
btn_search:SetPoint( "TopLeft", dropdown_skill, "TopRight", -5, 0 )
btn_search:SetHeight( 25 )
frame.btn_search = btn_search
UIDropDownMenu_Initialize( dropdown_skill, initialize_dropdown_skill )
UIDropDownMenu_SetWidth( 90, dropdown_skill )
UIDropDownMenu_SetText( "All tradeskills", dropdown_skill )
local border_results = m.FrameBuilder.new()
:parent( frame )
:point( "TopLeft", label_search, "BottomLeft", -2, -10 )
:point( "Right", btn_search, "Right", 0, 0 )
:height( 211 )
:frame_style( "TOOLTIP" )
:backdrop( { bgFile = "Interface/Buttons/WHITE8x8" } )
:backdrop_color( 0, 0, 0, 1 )
:build()
border_results:EnableMouseWheel( true )
border_results:SetScript( "OnMouseWheel", function()
local value = frame.scroll_bar:GetValue() - arg1
frame.scroll_bar:SetValue( value )
end )
frame.border_results = border_results
local scroll_bar = CreateFrame( "Slider", "GuildTradeskillsOctoScrollBar", border_results, "UIPanelScrollBarTemplate" )
scroll_bar:SetPoint( "TopRight", border_results, "TopRight", -5, -20 )
scroll_bar:SetPoint( "Bottom", border_results, "Bottom", 0, 20 )
scroll_bar:SetMinMaxValues( 0, 0 )
scroll_bar:SetValueStep( 1 )
scroll_bar:SetScript( "OnValueChanged", function()
offset = arg1
refresh()
end )
frame.scroll_bar = scroll_bar
for i = 1, 12 do -- Set to 12
local item = create_item( border_results, i )
item:SetPoint( "TopLeft", border_results, "TopLeft", 4, ((i - 1) * -17) - 4 )
table.insert( frame_items, item )
end
frame.crafters = create_crafters_frame( frame )
frame.info = create_info_frame( frame )
if not m.db.frame_tradeskills.show_reagents then
frame.info:Hide()
else
frame:SetHeight( 584 )
frame.crafters.btn_reagents:SetText( "Hide reagents" )
end
return frame
end
local function update_title()
if not popup or not popup.title_label then return end
local synced = m.db.players and m.count( m.db.players ) or 0
local online = 0
if m.db.players then
local online_roster = m.build_online_roster()
for _, player in pairs( m.db.players ) do
if online_roster[ player ] then
online = online + 1
end
end
end
popup.title_label:SetText( string.format(
"Guild Recipes Octo v%s | |cFFFFFF00Synced Players: %d|r | |cFF00FF00Online: %d|r",
m.version, synced, online
) )
end
local function show()
if not popup then
popup = create_frame()
end
update_title()
popup:Show()
popup.info.refresh()
end
local function hide()
if popup then
popup:Hide()
end
end
local function toggle()
if popup and popup:IsVisible() then
popup:Hide()
else
show()
end
end
local function is_visible()
return popup and popup:IsVisible() or false
end
local function update()
update_title()
popup.info.refresh()
end
return {
show = show,
hide = hide,
toggle = toggle,
is_visible = is_visible,
update = update,
}
end
m.Tradeskills = M
return M