TradeSkillMaster/Core/UI/CraftingUI/CraftingReports.lua

674 lines
22 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local CraftingReports = TSM.UI.CraftingUI:NewPackage("CraftingReports")
local L = TSM.Include("Locale").GetTable()
local Math = TSM.Include("Util.Math")
local Log = TSM.Include("Util.Log")
local Money = TSM.Include("Util.Money")
local String = TSM.Include("Util.String")
local ItemString = TSM.Include("Util.ItemString")
local Theme = TSM.Include("Util.Theme")
local ItemInfo = TSM.Include("Service.ItemInfo")
local Settings = TSM.Include("Service.Settings")
local UIElements = TSM.Include("UI.UIElements")
local private = {
settings = nil,
craftsQuery = nil,
matsQuery = nil,
filterText = "",
craftProfessions = {},
matProfessions = {},
}
local MAT_PRICE_SOURCES = {ALL, L["Default Price"], L["Custom Price"]}
-- ============================================================================
-- Module Functions
-- ============================================================================
function CraftingReports.OnInitialize()
private.settings = Settings.NewView()
:AddKey("global", "craftingUIContext", "craftsScrollingTable")
:AddKey("global", "craftingUIContext", "matsScrollingTable")
:AddKey("factionrealm", "internalData", "mats")
:AddKey("global", "craftingOptions", "defaultMatCostMethod")
TSM.UI.CraftingUI.RegisterTopLevelPage(L["Reports"], private.GetCraftingReportsFrame)
end
-- ============================================================================
-- CraftingReports UI
-- ============================================================================
function private.GetCraftingReportsFrame()
TSM.UI.AnalyticsRecordPathChange("crafting", "crafting_reports")
if not private.craftsQuery then
private.craftsQuery = TSM.Crafting.CreateCraftsQuery()
private.craftsQuery:VirtualField("firstOperation", "string", private.FirstOperationVirtualField, "itemString")
end
private.craftsQuery:ResetFilters()
private.craftsQuery:ResetOrderBy()
private.craftsQuery:OrderBy("itemName", true)
private.matsQuery = private.matsQuery or TSM.Crafting.CreateMatItemQuery()
private.matsQuery:ResetFilters()
private.matsQuery:ResetOrderBy()
private.matsQuery:OrderBy("name", true)
return UIElements.New("Frame", "craftingReportsContent")
:SetLayout("VERTICAL")
:SetPadding(0, 0, 6, 0)
:SetBackgroundColor("PRIMARY_BG_ALT")
:AddChild(UIElements.New("TabGroup", "buttons")
:SetNavCallback(private.GetTabElements)
:AddPath(L["Crafts"], true)
:AddPath(L["Materials"])
)
end
function private.GetTabElements(self, path)
if path == L["Crafts"] then
TSM.UI.AnalyticsRecordPathChange("crafting", "crafting_reports", "crafts")
private.filterText = ""
wipe(private.craftProfessions)
tinsert(private.craftProfessions, L["All Professions"])
for _, player, profession in TSM.Crafting.PlayerProfessions.Iterator() do
tinsert(private.craftProfessions, format("%s - %s", profession, player))
end
private.craftsQuery:ResetFilters()
return UIElements.New("Frame", "crafts")
:SetLayout("VERTICAL")
:AddChild(UIElements.New("Frame", "filters")
:SetLayout("HORIZONTAL")
:SetHeight(72)
:SetPadding(10, 10, 8, 16)
:AddChild(UIElements.New("Frame", "search")
:SetLayout("VERTICAL")
:AddChild(UIElements.New("Text", "label")
:SetHeight(20)
:SetMargin(0, 0, 0, 4)
:SetFont("BODY_BODY3_MEDIUM")
:SetText(L["Filter by Keyword"])
)
:AddChild(UIElements.New("Input", "input")
:SetHeight(24)
:AllowItemInsert()
:SetIconTexture("iconPack.18x18/Search")
:SetClearButtonEnabled(true)
:SetHintText(L["Enter Keyword"])
:SetScript("OnValueChanged", private.CraftsInputOnValueChanged)
)
)
:AddChild(UIElements.New("Frame", "profession")
:SetLayout("VERTICAL")
:SetMargin(16, 16, 0, 0)
:AddChild(UIElements.New("Text", "label")
:SetHeight(20)
:SetMargin(0, 0, 0, 4)
:SetFont("BODY_BODY3_MEDIUM")
:SetText(L["Filter by Profession"])
)
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
:SetHeight(24)
:SetItems(private.craftProfessions)
:SetSelectedItem(private.craftProfessions[1], true)
:SetScript("OnSelectionChanged", private.CraftsDropdownOnSelectionChanged)
)
)
:AddChild(UIElements.New("Frame", "craftable")
:SetLayout("HORIZONTAL")
:SetSize(176, 24)
:SetMargin(0, 0, 24, 0)
:AddChild(UIElements.New("Checkbox", "checkbox")
:SetWidth(24)
:SetFont("BODY_BODY2")
:SetScript("OnValueChanged", private.CheckboxOnValueChanged)
)
:AddChild(UIElements.New("Text", "label")
:SetWidth("AUTO")
:SetFont("BODY_BODY2")
:SetText(L["Only show craftable"])
)
)
)
:AddChild(UIElements.New("QueryScrollingTable", "crafts")
:SetSettingsContext(private.settings, "craftsScrollingTable")
:GetScrollingTableInfo()
:SetMenuInfo(private.CraftsMatsMenuIterator, private.CraftsMenuClickHandler)
:NewColumn("queued")
:SetTitleIcon("iconPack.18x18/Queue")
:SetFont("TABLE_TABLE1")
:SetJustifyH("CENTER")
:SetTextInfo("num")
:SetSortInfo("num")
:Commit()
:NewColumn("craftName")
:SetTitle(L["Name"])
:SetIconSize(12)
:SetFont("ITEM_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo(nil, private.CraftsGetCraftNameText)
:SetIconInfo("itemString", ItemInfo.GetTexture)
:SetTooltipInfo("itemString")
:SetSortInfo("itemName")
:DisableHiding()
:Commit()
:NewColumn("operation")
:SetTitle(L["Operation"])
:SetFont("BODY_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo("firstOperation")
:SetSortInfo("firstOperation")
:Commit()
:NewColumn("bags")
:SetTitle(L["Bag"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo("bagQuantity", private.CraftsGetBagsText)
:SetSortInfo("bagQuantity")
:Commit()
:NewColumn("ah")
:SetTitle(L["AH"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo("auctionQuantity", private.CraftsGetAHText)
:SetSortInfo("auctionQuantity")
:Commit()
:NewColumn("craftingCost")
:SetTitle(L["Crafting Cost"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo("craftingCost", private.CraftsGetCostItemValueText)
:SetSortInfo("craftingCost")
:Commit()
:NewColumn("itemValue")
:SetTitle(L["Item Value"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo("itemValue", private.CraftsGetCostItemValueText)
:SetSortInfo("itemValue")
:Commit()
:NewColumn("profit")
:SetTitle(L["Profit"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo("profit", private.CraftsGetProfitText)
:SetSortInfo("profit")
:Commit()
:NewColumn("profitPct")
:SetTitle("%")
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo("profitPct", private.GetProfitPctText)
:SetSortInfo("profitPct")
:Commit()
:NewColumn("saleRate")
:SetTitleIcon("iconPack.18x18/SaleRate")
:SetFont("TABLE_TABLE1")
:SetJustifyH("CENTER")
:SetTextInfo("saleRate", private.CraftsGetSaleRateText)
:SetSortInfo("saleRate")
:Commit()
:Commit()
:SetQuery(private.craftsQuery)
:SetSelectionDisabled(true)
:SetScript("OnRowClick", private.CraftsOnRowClick)
)
elseif path == L["Materials"] then
TSM.UI.AnalyticsRecordPathChange("crafting", "crafting_reports", "materials")
wipe(private.matProfessions)
tinsert(private.matProfessions, L["All Professions"])
for _, _, profession in TSM.Crafting.PlayerProfessions.Iterator() do
if not private.matProfessions[profession] then
tinsert(private.matProfessions, profession)
private.matProfessions[profession] = true
end
end
private.matsQuery:ResetFilters()
return UIElements.New("Frame", "materials")
:SetLayout("VERTICAL")
:AddChild(UIElements.New("Frame", "filters")
:SetLayout("HORIZONTAL")
:SetHeight(72)
:SetPadding(10, 10, 8, 16)
:AddChild(UIElements.New("Frame", "search")
:SetLayout("VERTICAL")
:AddChild(UIElements.New("Text", "label")
:SetHeight(20)
:SetMargin(0, 0, 0, 4)
:SetFont("BODY_BODY3_MEDIUM")
:SetText(L["Filter by Keyword"])
)
:AddChild(UIElements.New("Input", "input")
:SetHeight(24)
:AllowItemInsert()
:SetIconTexture("iconPack.18x18/Search")
:SetClearButtonEnabled(true)
:SetHintText(L["Enter Keyword"])
:SetScript("OnValueChanged", private.MatsInputOnValueChanged)
)
)
:AddChild(UIElements.New("Frame", "profession")
:SetLayout("VERTICAL")
:SetMargin(16, 0, 0, 0)
:AddChild(UIElements.New("Text", "label")
:SetHeight(20)
:SetMargin(0, 0, 0, 4)
:SetFont("BODY_BODY3_MEDIUM")
:SetText(L["Filter by Profession"])
)
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
:SetHeight(24)
:SetItems(private.matProfessions)
:SetSelectedItem(private.matProfessions[1], true)
:SetScript("OnSelectionChanged", private.MatsDropdownOnSelectionChanged)
)
)
:AddChild(UIElements.New("Frame", "priceSource")
:SetLayout("VERTICAL")
:SetMargin(16, 0, 0, 0)
:AddChild(UIElements.New("Text", "label")
:SetHeight(20)
:SetMargin(0, 0, 0, 4)
:SetFont("BODY_BODY3_MEDIUM")
:SetText(L["Filter by Price Source"])
)
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
:SetHeight(24)
:SetItems(MAT_PRICE_SOURCES)
:SetSelectedItem(MAT_PRICE_SOURCES[1], true)
:SetScript("OnSelectionChanged", private.MatsDropdownOnSelectionChanged)
)
)
)
:AddChild(UIElements.New("QueryScrollingTable", "mats")
:SetSettingsContext(private.settings, "matsScrollingTable")
:GetScrollingTableInfo()
:SetMenuInfo(private.CraftsMatsMenuIterator, private.MatsMenuClickHandler)
:NewColumn("name")
:SetTitle(L["Name"])
:SetIconSize(12)
:SetFont("ITEM_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo("itemString", private.MatsGetNameText)
:SetIconInfo("itemString", ItemInfo.GetTexture)
:SetTooltipInfo("itemString")
:SetSortInfo("name")
:DisableHiding()
:Commit()
:NewColumn("price")
:SetTitle(L["Mat Price"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo("matCost", private.MatsGetPriceText)
:SetSortInfo("matCost")
:Commit()
:NewColumn("professions")
:SetTitle(L["Professions Used In"])
:SetFont("ITEM_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo("professions")
:SetSortInfo("professions")
:Commit()
:NewColumn("num")
:SetTitle(L["Number Owned"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo("totalQuantity", private.MatsGetNumText)
:SetSortInfo("totalQuantity")
:Commit()
:Commit()
:SetQuery(private.matsQuery)
:SetSelectionDisabled(true)
:SetScript("OnRowClick", private.MatsOnRowClick)
)
else
error("Unknown path: "..tostring(path))
end
end
-- ============================================================================
-- ScrollingTable Functions
-- ============================================================================
function private.CraftsGetCraftNameText(row)
return TSM.UI.GetColoredItemName(row:GetField("itemString")) or row:GetField("name")
end
function private.CraftsGetBagsText(bagQuantity)
return bagQuantity or "0"
end
function private.CraftsGetAHText(bagQuantity)
return bagQuantity or "0"
end
function private.CraftsGetCostItemValueText(costItemValue)
if Math.IsNan(costItemValue) then
return ""
end
return Money.ToString(costItemValue)
end
function private.CraftsGetProfitText(profit)
if Math.IsNan(profit) then
return ""
end
return Money.ToString(profit, (profit >= 0 and Theme.GetFeedbackColor("GREEN") or Theme.GetFeedbackColor("RED")):GetTextColorPrefix())
end
function private.GetProfitPctText(profitPct)
if Math.IsNan(profitPct) then
return ""
end
local color = Theme.GetFeedbackColor(profitPct >= 0 and "GREEN" or "RED")
return color:ColorText(profitPct.."%") or ""
end
function private.CraftsGetSaleRateText(saleRate)
if Math.IsNan(saleRate) then
return ""
end
return format("%0.2f", saleRate)
end
function private.MatsGetNameText(itemString)
return TSM.UI.GetColoredItemName(itemString) or TSM.UI.GetColoredItemName(ItemString.GetUnknown())
end
function private.MatsGetPriceText(matCost)
if Math.IsNan(matCost) then
return ""
end
return Money.ToString(matCost)
end
function private.MatsGetNumText(totalQuantity)
return totalQuantity or "0"
end
-- ============================================================================
-- Local Script Handlers
-- ============================================================================
function private.CraftsInputOnValueChanged(input)
local text = input:GetValue()
if text == private.filterText then
return
end
private.filterText = text
private.UpdateCraftsQueryWithFilters(input:GetParentElement():GetParentElement())
end
function private.CraftsDropdownOnSelectionChanged(dropdown)
private.UpdateCraftsQueryWithFilters(dropdown:GetParentElement():GetParentElement())
end
function private.CheckboxOnValueChanged(checkbox)
private.UpdateCraftsQueryWithFilters(checkbox:GetParentElement():GetParentElement())
end
function private.CraftsOnRowClick(scrollingTable, record, mouseButton)
if mouseButton == "LeftButton" then
TSM.Crafting.Queue.Add(record:GetField("spellId"), 1)
elseif mouseButton == "RightButton" then
TSM.Crafting.Queue.Remove(record:GetField("spellId"), 1)
end
scrollingTable:Draw()
end
function private.MatsInputOnValueChanged(input)
private.UpdateMatsQueryWithFilters(input:GetParentElement():GetParentElement())
end
function private.MatsDropdownOnSelectionChanged(dropdown)
private.UpdateMatsQueryWithFilters(dropdown:GetParentElement():GetParentElement())
end
function private.MatsOnRowClick(scrollingTable, row)
local itemString = row:GetField("itemString")
local priceStr = private.settings.mats[itemString].customValue or private.settings.defaultMatCostMethod
scrollingTable:GetBaseElement():ShowDialogFrame(UIElements.New("Frame", "frame")
:SetLayout("VERTICAL")
:SetSize(478, 312)
:SetPadding(12)
:AddAnchor("CENTER")
:SetBackgroundColor("FRAME_BG", true)
:SetContext(itemString)
:AddChild(UIElements.New("Frame", "header")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:SetMargin(0, 0, -4, 10)
:AddChild(UIElements.New("Spacer", "spacer")
:SetWidth(20)
)
:AddChild(UIElements.New("Text", "title")
:SetJustifyH("CENTER")
:SetFont("BODY_BODY1_BOLD")
:SetText(L["Edit Material Price"])
)
:AddChild(UIElements.New("Button", "closeBtn")
:SetMargin(0, -4, 0, 0)
:SetBackgroundAndSize("iconPack.24x24/Close/Default")
:SetScript("OnClick", private.DialogCloseBtnOnClick)
)
)
:AddChild(UIElements.New("Frame", "item")
:SetLayout("HORIZONTAL")
:SetPadding(6)
:SetMargin(0, 0, 0, 10)
:SetBackgroundColor("PRIMARY_BG_ALT", true)
:AddChild(UIElements.New("Button", "icon")
:SetSize(36, 36)
:SetMargin(0, 8, 0, 0)
:SetBackground(ItemInfo.GetTexture(itemString))
:SetTooltip(itemString)
)
:AddChild(UIElements.New("Text", "name")
:SetHeight(36)
:SetFont("ITEM_BODY1")
:SetText(TSM.UI.GetColoredItemName(itemString))
)
)
:AddChild(UIElements.New("Text", "desc")
:SetHeight(20)
:SetMargin(0, 0, 0, 6)
:SetFont("BODY_BODY2_MEDIUM")
:SetText(L["Material Price"])
)
:AddChild(UIElements.New("MultiLineInput", "input")
:SetMargin(0, 0, 0, 12)
:SetBackgroundColor("PRIMARY_BG_ALT")
:SetFont("BODY_BODY2_MEDIUM")
:SetValidateFunc("CUSTOM_PRICE")
:SetValue(Money.ToString(priceStr) or priceStr)
:SetScript("OnValueChanged", private.MatPriceInputOnValueChanged)
)
:AddChild(UIElements.New("Frame", "buttons")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:AddChild(UIElements.New("Button", "resetBtn")
:SetWidth("AUTO")
:SetFont("BODY_BODY3_MEDIUM")
:SetTextColor(private.settings.mats[itemString].customValue and "TEXT" or "TEXT_ALT")
:SetDisabled(not private.settings.mats[itemString].customValue)
:SetText(L["Reset to Default"])
:SetScript("OnClick", private.ResetButtonOnClick)
)
:AddChild(UIElements.New("Frame", "spacer"))
:AddChild(UIElements.New("ActionButton", "closeBtn")
:SetWidth(342)
:SetText(L["Save"])
:SetScript("OnClick", private.DialogCloseBtnOnClick)
)
)
)
end
function private.DialogCloseBtnOnClick(button)
button:GetBaseElement():HideDialog()
end
function private.MatPriceInputOnValueChanged(input)
local value = input:GetValue()
local itemString = input:GetParentElement():GetContext()
TSM.Crafting.SetMatCustomValue(itemString, value)
input:GetElement("__parent.buttons.resetBtn")
:SetTextColor("TEXT")
:SetDisabled(false)
:Draw()
end
function private.ResetButtonOnClick(button)
local itemString = button:GetParentElement():GetParentElement():GetContext()
TSM.Crafting.SetMatCustomValue(itemString, nil)
assert(not private.settings.mats[itemString].customValue)
button:SetTextColor("TEXT_ALT")
button:SetDisabled(true)
button:Draw()
button:GetElement("__parent.__parent.input")
:SetValue(Money.ToString(private.settings.defaultMatCostMethod) or private.settings.defaultMatCostMethod)
:Draw()
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.FirstOperationVirtualField(itemString)
return TSM.Operations.GetFirstOperationByItem("Crafting", itemString) or ""
end
function private.UpdateCraftsQueryWithFilters(frame)
private.craftsQuery:ResetFilters()
-- apply search filter
local filter = strtrim(frame:GetElement("search.input"):GetValue())
if filter ~= "" then
private.craftsQuery:Matches("itemName", String.Escape(filter))
end
-- apply dropdown filter
local professionPlayer = frame:GetElement("profession.dropdown"):GetSelectedItem()
if professionPlayer ~= private.craftProfessions[1] then
local profession, player = strmatch(professionPlayer, "^(.+) %- ([^ ]+)$")
private.craftsQuery
:Equal("profession", profession)
:Or()
:Equal("players", player)
:Matches("players", "^"..player..",")
:Matches("players", ","..player..",")
:Matches("players", ","..player.."$")
:End()
end
-- apply craftable filter
local craftableOnly = frame:GetElement("craftable.checkbox"):IsChecked()
if craftableOnly then
private.craftsQuery:Custom(private.IsCraftableQueryFilter)
end
frame:GetElement("__parent.crafts"):SetQuery(private.craftsQuery, true)
end
function private.IsCraftableQueryFilter(record)
return TSM.Crafting.ProfessionUtil.GetNumCraftableFromDB(record:GetField("spellId")) > 0
end
function private.UpdateMatsQueryWithFilters(frame)
private.matsQuery:ResetFilters()
-- apply search filter
local filter = strtrim(frame:GetElement("search.input"):GetValue())
if filter ~= "" then
private.matsQuery:Custom(private.MatItemNameQueryFilter, strlower(String.Escape(filter)))
end
-- apply dropdown filters
local profession = frame:GetElement("profession.dropdown"):GetSelectedItem()
if profession ~= private.matProfessions[1] then
private.matsQuery
:Or()
:Equal("professions", profession)
:Matches("professions", "^"..profession..",")
:Matches("professions", ","..profession..",")
:Matches("professions", ","..profession.."$")
:End()
end
local priceSource = frame:GetElement("priceSource.dropdown"):GetSelectedItem()
if priceSource == MAT_PRICE_SOURCES[2] then
private.matsQuery:Equal("customValue", "")
elseif priceSource == MAT_PRICE_SOURCES[3] then
private.matsQuery:NotEqual("customValue", "")
end
frame:GetElement("__parent.mats"):SetQuery(private.matsQuery, true)
end
function private.MatItemNameQueryFilter(row, filter)
local name = ItemInfo.GetName(row:GetField("itemString"))
if not name then return end
return strmatch(strlower(name), filter)
end
function private.CraftsMatsMenuIterator(scrollingTable, prevIndex)
if prevIndex == "CREATE_GROUPS" then
-- we're done
return
else
return "CREATE_GROUPS", L["Create Groups from Table"]
end
end
function private.CraftsMenuClickHandler(scrollingTable, index1, index2)
if index1 == "CREATE_GROUPS" then
assert(not index2)
scrollingTable:GetBaseElement():HideDialog()
local numCreated, numAdded = 0, 0
for _, row in private.craftsQuery:Iterator() do
local itemString = row:GetField("itemString")
local groupPath = TSM.Groups.Path.Join(L["Crafted Items"], row:GetField("profession"))
if not TSM.Groups.Exists(groupPath) then
TSM.Groups.Create(groupPath)
numCreated = numCreated + 1
end
if not TSM.Groups.IsItemInGroup(itemString) and not ItemInfo.IsSoulbound(itemString) then
TSM.Groups.SetItemGroup(itemString, groupPath)
numAdded = numAdded + 1
end
end
Log.PrintfUser(L["%d groups were created and %d items were added from the table."], numCreated, numAdded)
else
error("Unexpected index1: "..tostring(index1))
end
end
function private.MatsMenuClickHandler(scrollingTable, index1, index2)
if index1 == "CREATE_GROUPS" then
assert(not index2)
scrollingTable:GetBaseElement():HideDialog()
local numCreated, numAdded = 0, 0
for _, row in private.matsQuery:Iterator() do
local itemString = row:GetField("itemString")
local groupPath = L["Materials"]
if not TSM.Groups.Exists(groupPath) then
TSM.Groups.Create(groupPath)
numCreated = numCreated + 1
end
if not TSM.Groups.IsItemInGroup(itemString) and not ItemInfo.IsSoulbound(itemString) then
TSM.Groups.SetItemGroup(itemString, groupPath)
numAdded = numAdded + 1
end
end
Log.PrintfUser(L["%d groups were created and %d items were added from the table."], numCreated, numAdded)
else
error("Unexpected index1: "..tostring(index1))
end
end