-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- https://tradeskillmaster.com -- -- All Rights Reserved - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- local _, TSM = ... local MyAuctions = TSM.UI.AuctionUI:NewPackage("MyAuctions") local L = TSM.Include("Locale").GetTable() local FSM = TSM.Include("Util.FSM") local Money = TSM.Include("Util.Money") local String = TSM.Include("Util.String") local Math = TSM.Include("Util.Math") local TempTable = TSM.Include("Util.TempTable") 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, fsm = nil, frame = nil, query = nil, } local DURATION_LIST = { L["None"], AUCTION_TIME_LEFT1_DETAIL, AUCTION_TIME_LEFT2_DETAIL, AUCTION_TIME_LEFT3_DETAIL, AUCTION_TIME_LEFT4_DETAIL, } local SECONDS_PER_MIN = 60 local SECONDS_PER_HOUR = 60 * SECONDS_PER_MIN -- ============================================================================ -- Module Functions -- ============================================================================ function MyAuctions.OnInitialize() private.settings = Settings.NewView() :AddKey("global", "auctionUIContext", "myAuctionsScrollingTable") private.FSMCreate() TSM.UI.AuctionUI.RegisterTopLevelPage(L["My Auctions"], private.GetMyAuctionsFrame, private.OnItemLinked) end -- ============================================================================ -- MyAuctions UI -- ============================================================================ function private.GetMyAuctionsFrame() TSM.UI.AnalyticsRecordPathChange("auction", "my_auctions") private.query = private.query or TSM.MyAuctions.CreateQuery() local frame = UIElements.New("Frame", "myAuctions") :SetLayout("VERTICAL") :SetBackgroundColor("PRIMARY_BG_ALT") :AddChild(UIElements.New("Frame", "header") :SetLayout("VERTICAL") :SetHeight(72) :SetPadding(8) :AddChild(UIElements.New("Frame", "filters") :SetLayout("HORIZONTAL") :SetHeight(24) :SetMargin(0, 0, 0, 8) :AddChild(UIElements.New("SelectionDropdown", "duration") :SetWidth(232) :SetItems(DURATION_LIST) :SetHintText(L["Filter by duration"]) :SetScript("OnSelectionChanged", private.DurationFilterChanged) ) :AddChild(UIElements.New("GroupSelector", "group") :SetWidth(232) :SetMargin(8, 8, 0, 0) :SetHintText(L["Filter by groups"]) :SetScript("OnSelectionChanged", private.FilterChanged) ) :AddChild(UIElements.New("Input", "keyword") :SetIconTexture("iconPack.18x18/Search") :AllowItemInsert(false) :SetClearButtonEnabled(true) :SetHintText(L["Filter by keyword"]) :SetScript("OnValueChanged", private.FilterChanged) ) ) :AddChild(UIElements.New("Frame", "buttons") :SetLayout("HORIZONTAL") :SetHeight(24) :AddChild(UIElements.New("Checkbox", "ignoreBid") :SetWidth("AUTO") :SetCheckboxPosition("LEFT") :SetText(L["Hide auctions with bids"]) :SetScript("OnValueChanged", private.ToggleFilterChanged) ) :AddChild(UIElements.New("Checkbox", "onlyBid") :SetMargin(16, 0, 0, 0) :SetWidth("AUTO") :SetCheckboxPosition("LEFT") :SetText(L["Show only auctions with bids"]) :SetScript("OnValueChanged", private.ToggleFilterChanged) ) :AddChild(UIElements.New("Checkbox", "onlySold") :SetMargin(16, 0, 0, 0) :SetWidth("AUTO") :SetCheckboxPosition("LEFT") :SetText(L["Only show sold auctions"]) :SetScript("OnValueChanged", private.ToggleFilterChanged) ) :AddChild(UIElements.New("Spacer")) :AddChild(UIElements.New("ActionButton", "clearfilter") :SetSize(142, 24) :SetText(L["Clear Filters"]) :SetScript("OnClick", private.ClearFilterOnClick) ) ) ) if TSM.IsWowClassic() then frame:AddChild(UIElements.New("MyAuctionsScrollingTable", "auctions") :SetSettingsContext(private.settings, "myAuctionsScrollingTable") :GetScrollingTableInfo() :NewColumn("item") :SetTitle(L["Item Name"]) :SetIconSize(12) :SetFont("ITEM_BODY3") :SetJustifyH("LEFT") :SetTextInfo(nil, private.AuctionsGetItemText) :SetIconInfo("itemString", ItemInfo.GetTexture) :SetTooltipInfo("itemString", private.AuctionsGetItemTooltip) :SetActionIconInfo(1, 12, private.AuctionsGetSoldIcon) :DisableHiding() :Commit() :NewColumn("stackSize") :SetTitle(L["Qty"]) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo(nil, private.AuctionsGetStackSizeText) :Commit() :NewColumn("timeLeft") :SetTitleIcon("iconPack.14x14/Clock") :SetFont("BODY_BODY3") :SetJustifyH("CENTER") :SetTextInfo(nil, private.AuctionsGetTimeLeftText) :Commit() :NewColumn("highbidder") :SetTitle(L["High Bidder"]) :SetFont("BODY_BODY3") :SetJustifyH("LEFT") :SetTextInfo(nil, private.AuctionsGetHighBidderText) :Commit() :NewColumn("group") :SetTitle(GROUP) :SetFont("BODY_BODY3") :SetJustifyH("LEFT") :SetTextInfo(nil, private.AuctionsGetGroupNameText) :Commit() :NewColumn("currentBid") :SetTitle(BID) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo(nil, private.AuctionsGetCurrentBidText) :Commit() :NewColumn("buyout") :SetTitle(BUYOUT) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo(nil, private.AuctionsGetCurrentBuyoutText) :Commit() :Commit() :SetQuery(private.query) :SetSelectionValidator(private.AuctionsValidateSelection) :SetScript("OnSelectionChanged", private.AuctionsOnSelectionChanged) :SetScript("OnDataUpdated", private.AuctionsOnDataUpdated) ) else frame:AddChild(UIElements.New("MyAuctionsScrollingTable", "auctions") :SetSettingsContext(private.settings, "myAuctionsScrollingTable") :GetScrollingTableInfo() :NewColumn("item") :SetTitle(L["Item Name"]) :SetIconSize(12) :SetFont("ITEM_BODY3") :SetJustifyH("LEFT") :SetTextInfo(nil, private.AuctionsGetItemText) :SetIconInfo("itemString", ItemInfo.GetTexture) :SetTooltipInfo("itemString", private.AuctionsGetItemTooltip) :SetActionIconInfo(1, 12, private.AuctionsGetSoldIcon) :SetSortInfo("name") :DisableHiding() :Commit() :NewColumn("stackSize") :SetTitle(L["Qty"]) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo(nil, private.AuctionsGetStackSizeText) :SetSortInfo("stackSize") :Commit() :NewColumn("timeLeft") :SetTitleIcon("iconPack.14x14/Clock") :SetFont("BODY_BODY3") :SetJustifyH("CENTER") :SetTextInfo(nil, private.AuctionsGetTimeLeftText) :SetSortInfo("duration") :Commit() :NewColumn("group") :SetTitle(GROUP) :SetFont("BODY_BODY3") :SetJustifyH("LEFT") :SetTextInfo(nil, private.AuctionsGetGroupNameText) :SetSortInfo("group") :Commit() :NewColumn("currentBid") :SetTitle(BID) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo(nil, private.AuctionsGetCurrentBidText) :SetSortInfo("currentBid") :Commit() :NewColumn("buyout") :SetTitle(BUYOUT) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo(nil, private.AuctionsGetCurrentBuyoutText) :SetSortInfo("buyout") :Commit() :Commit() :SetQuery(private.query) :SetSelectionValidator(private.AuctionsValidateSelection) :SetScript("OnSelectionChanged", private.AuctionsOnSelectionChanged) :SetScript("OnDataUpdated", private.AuctionsOnDataUpdated) ) end frame:AddChild(UIElements.New("Texture", "line") :SetHeight(2) :SetTexture("ACTIVE_BG") ) :AddChild(UIElements.New("Frame", "bottom") :SetLayout("VERTICAL") :SetHeight(68) :SetPadding(8) :SetBackgroundColor("PRIMARY_BG_ALT") :AddChild(UIElements.New("Frame", "row1") :SetLayout("HORIZONTAL") :SetHeight(20) :SetMargin(0, 0, 0, 8) :AddChild(UIElements.New("Text", "sold") :SetWidth("AUTO") :SetFont("BODY_BODY3_MEDIUM") ) :AddChild(UIElements.New("Text", "soldValue") :SetWidth("AUTO") :SetFont("TABLE_TABLE1") ) :AddChild(UIElements.New("Texture", "vline") :SetWidth(1) :SetMargin(8, 8, 0, 0) :SetTexture("ACTIVE_BG_ALT") ) :AddChild(UIElements.New("Text", "posted") :SetWidth("AUTO") :SetFont("BODY_BODY3_MEDIUM") ) :AddChild(UIElements.New("Text", "postedValue") :SetWidth("AUTO") :SetFont("TABLE_TABLE1") ) :AddChild(UIElements.New("Spacer", "spacer")) ) :AddChild(UIElements.New("Frame", "row2") :SetLayout("HORIZONTAL") :SetHeight(24) :AddChild(UIElements.New("ProgressBar", "progressBar") :SetMargin(0, 8, 0, 0) :SetProgress(0) ) :AddChild(UIElements.NewNamed("ActionButton", "cancelBtn", "TSMCancelAuctionBtn") :SetSize(110, 24) :SetMargin(0, 8, 0, 0) :SetText(CANCEL) :SetDisabled(true) :DisableClickCooldown(true) :SetScript("OnClick", private.CancelButtonOnClick) ) :AddChild(UIElements.New("ActionButton", "skipBtn") :SetSize(110, 24) :SetText(L["Skip"]) :SetDisabled(true) :DisableClickCooldown(true) :SetScript("OnClick", private.SkipButtonOnClick) ) ) ) :SetScript("OnUpdate", private.FrameOnUpdate) :SetScript("OnHide", private.FrameOnHide) private.frame = frame return frame end -- ============================================================================ -- Local Script Handlers -- ============================================================================ function private.OnItemLinked(name) private.frame:GetElement("header.filters.keyword") :SetValue(name) :Draw() private.FilterChanged() return true end function private.FrameOnUpdate(frame) frame:SetScript("OnUpdate", nil) private.fsm:ProcessEvent("EV_FRAME_SHOWN", frame) end function private.FrameOnHide(frame) assert(frame == private.frame) private.frame = nil private.fsm:ProcessEvent("EV_FRAME_HIDDEN") end function private.ToggleFilterChanged(toggle) local ignoreBidToggle = toggle:GetElement("__parent.ignoreBid") local onlyBidToggle = toggle:GetElement("__parent.onlyBid") local onlySoldToggle = toggle:GetElement("__parent.onlySold") if ignoreBidToggle:IsChecked() and onlyBidToggle:IsChecked() then -- uncheck the other toggle in the pair of "bid" toggles if toggle == ignoreBidToggle then onlyBidToggle:SetChecked(false, true) :Draw() else ignoreBidToggle:SetChecked(false, true) :Draw() end end if onlyBidToggle:IsChecked() and onlySoldToggle:IsChecked() then -- uncheck the other toggle in the pair of "only" toggles if toggle == onlyBidToggle then onlySoldToggle:SetChecked(false, true) :Draw() else onlyBidToggle:SetChecked(false, true) :Draw() end end private.FilterChanged() end function private.DurationFilterChanged(dropdown) if dropdown:GetSelectedItemKey() == 1 then -- none dropdown:SetSelectedItem(nil, true) end private.FilterChanged() end function private.FilterChanged() private.fsm:ProcessEvent("EV_FILTER_UPDATED") end function private.ClearFilterOnClick(button) button:GetElement("__parent.__parent.filters.duration") :SetOpen(false) :SetSelectedItem() button:GetElement("__parent.__parent.filters.group") :ClearSelectedGroups() button:GetElement("__parent.__parent.filters.keyword") :SetValue("") :SetFocused(false) button:GetElement("__parent.ignoreBid") :SetChecked(false, true) button:GetElement("__parent.onlyBid") :SetChecked(false, true) button:GetElement("__parent.onlySold") :SetChecked(false, true) button:GetParentElement():GetParentElement():Draw() private.fsm:ProcessEvent("EV_FILTER_UPDATED") end function private.AuctionsValidateSelection(_, row) return row:GetField("saleStatus") == 0 end function private.AuctionsOnSelectionChanged() private.fsm:ProcessEvent("EV_SELECTION_CHANGED") end function private.AuctionsOnDataUpdated() if not private.frame then return end private.fsm:SetLoggingEnabled(false) private.fsm:ProcessEvent("EV_DATA_UPDATED") private.fsm:SetLoggingEnabled(true) end function private.CancelButtonOnClick(button) private.fsm:ProcessEvent("EV_CANCEL_CLICKED") end function private.SkipButtonOnClick(button) private.fsm:ProcessEvent("EV_SKIP_CLICKED") end -- ============================================================================ -- FSM -- ============================================================================ function private.FSMCreate() local fsmContext = { frame = nil, durationFilter = nil, keywordFilter = nil, groupFilter = {}, bidFilter = nil, soldFilter = false, filterChanged = false, } local function UpdateFrame(context) if not context.frame then return end local auctions = context.frame:GetElement("auctions") if context.filterChanged then context.filterChanged = false private.query:ResetFilters() if context.durationFilter then if TSM.IsWowClassic() then private.query:Equal("duration", context.durationFilter) else if context.durationFilter == 1 then private.query:LessThan("duration", time() + (30 * SECONDS_PER_MIN)) elseif context.durationFilter == 2 then private.query:LessThan("duration", time() + (2 * SECONDS_PER_HOUR)) elseif context.durationFilter == 3 then private.query:LessThanOrEqual("duration", time() + (12 * SECONDS_PER_HOUR)) else private.query:GreaterThan("duration", time() + (12 * SECONDS_PER_HOUR)) end end end if context.keywordFilter then private.query:Matches("itemName", context.keywordFilter) end if next(context.groupFilter) then private.query:InTable("group", context.groupFilter) end if context.bidFilter ~= nil then if context.bidFilter then private.query:NotEqual("highBidder", "") else private.query:Equal("highBidder", "") end end if context.soldFilter then private.query:Equal("saleStatus", 1) end auctions:SetQuery(private.query, true) end local selectedRow = auctions:GetSelection() local hasFilter = context.durationFilter or context.keywordFilter or next(context.groupFilter) or context.bidFilter ~= nil or context.soldFilter context.frame:GetElement("header.buttons.clearfilter") :SetDisabled(not hasFilter) :Draw() local numPending = TSM.MyAuctions.GetNumPending() local progressText = nil if numPending > 0 then progressText = format(L["Canceling %d Auctions..."], numPending) elseif selectedRow then progressText = L["Ready to Cancel"] else progressText = L["Select Auction to Cancel"] end local row2 = context.frame:GetElement("bottom.row2") row2:GetElement("cancelBtn") :SetDisabled(not selectedRow or (not TSM.IsWowClassic() and numPending > 0) or (not TSM.IsWowClassic() and C_AuctionHouse.GetCancelCost(selectedRow:GetField("auctionId")) > GetMoney())) row2:GetElement("skipBtn") :SetDisabled(not selectedRow) row2:GetElement("progressBar") :SetProgressIconHidden(numPending == 0) :SetText(progressText) row2:Draw() local numPosted, numSold, postedGold, soldGold = 0, 0, 0, 0 for _, row in private.query:Iterator() do local itemString, saleStatus, buyout, currentBid, stackSize = row:GetFields("itemString", "saleStatus", "buyout", "currentBid", "stackSize") if saleStatus == 1 then numSold = numSold + 1 -- if somebody did a buyout, then bid will be equal to buyout, otherwise it'll be the winning bid soldGold = soldGold + currentBid else numPosted = numPosted + 1 if ItemInfo.IsCommodity(itemString) then postedGold = postedGold + (buyout * stackSize) else postedGold = postedGold + buyout end end end local row1 = context.frame:GetElement("bottom.row1") row1:GetElement("sold") :SetFormattedText((hasFilter and L["%s Sold Auctions (Filtered)"] or L["%s Sold Auctions"])..":", Theme.GetColor("INDICATOR"):ColorText(numSold)) row1:GetElement("soldValue") :SetText(Money.ToString(soldGold)) row1:GetElement("posted") :SetFormattedText((hasFilter and L["%s Posted Auctions (Filtered)"] or L["%s Posted Auctions"])..":", Theme.GetColor("INDICATOR_ALT"):ColorText(numPosted)) row1:GetElement("postedValue") :SetText(Money.ToString(postedGold)) row1:Draw() end private.fsm = FSM.New("MY_AUCTIONS") :AddState(FSM.NewState("ST_HIDDEN") :SetOnEnter(function(context) context.frame = nil context.durationFilter = nil context.keywordFilter = nil wipe(context.groupFilter) context.bidFilter = nil context.soldFilter = false end) :AddTransition("ST_HIDDEN") :AddTransition("ST_SHOWNING") :AddEventTransition("EV_FRAME_SHOWN", "ST_SHOWNING") ) :AddState(FSM.NewState("ST_SHOWNING") :SetOnEnter(function(context, frame) context.frame = frame context.filterChanged = true return "ST_SHOWN" end) :AddTransition("ST_SHOWN") ) :AddState(FSM.NewState("ST_SHOWN") :SetOnEnter(function(context) UpdateFrame(context) end) :AddTransition("ST_HIDDEN") :AddTransition("ST_SHOWN") :AddTransition("ST_CANCELING") :AddEventTransition("EV_CANCEL_CLICKED", "ST_CANCELING") :AddEventTransition("EV_SELECTION_CHANGED", "ST_SHOWN") :AddEventTransition("EV_DATA_UPDATED", "ST_SHOWN") :AddEvent("EV_SKIP_CLICKED", function(context) context.frame:GetElement("auctions"):SelectNextRow() return "ST_SHOWN" end) :AddEvent("EV_FILTER_UPDATED", function(context) local didChange = false local durationFilter = context.frame:GetElement("header.filters.duration"):GetSelectedItemKey() durationFilter = durationFilter and (durationFilter - 1) or nil if durationFilter ~= context.durationFilter then context.durationFilter = durationFilter didChange = true end local keywordFilter = context.frame:GetElement("header.filters.keyword"):GetValue() keywordFilter = keywordFilter ~= "" and String.Escape(keywordFilter) or nil if keywordFilter ~= context.keywordFilter then context.keywordFilter = keywordFilter didChange = true end local newGroupFilter = TempTable.Acquire() for groupPath in context.frame:GetElement("header.filters.group"):SelectedGroupIterator() do newGroupFilter[groupPath] = true end if Math.CalculateHash(newGroupFilter) ~= Math.CalculateHash(context.groupFilter) then didChange = true wipe(context.groupFilter) for groupPath in pairs(newGroupFilter) do context.groupFilter[groupPath] = true end end TempTable.Release(newGroupFilter) local bidFilter = nil if context.frame:GetElement("header.buttons.ignoreBid"):IsChecked() then bidFilter = false elseif context.frame:GetElement("header.buttons.onlyBid"):IsChecked() then bidFilter = true end if bidFilter ~= context.bidFilter then context.bidFilter = bidFilter didChange = true end local soldFilter = context.frame:GetElement("header.buttons.onlySold"):IsChecked() if soldFilter ~= context.soldFilter then context.soldFilter = soldFilter didChange = true end if didChange then context.filterChanged = true return "ST_SHOWN" end end) ) :AddState(FSM.NewState("ST_CANCELING") :SetOnEnter(function(context) local buttonsFrame = context.frame:GetElement("bottom") buttonsFrame:GetElement("row2.cancelBtn"):SetDisabled(true) buttonsFrame:GetElement("row2.skipBtn"):SetDisabled(true) buttonsFrame:Draw() local auctionId = context.frame:GetElement("auctions"):GetSelection():GetField("auctionId") if TSM.MyAuctions.CanCancel(auctionId) then TSM.MyAuctions.CancelAuction(auctionId) end return "ST_SHOWN" end) :AddTransition("ST_HIDDEN") :AddTransition("ST_SHOWN") ) :AddDefaultEventTransition("EV_FRAME_HIDDEN", "ST_HIDDEN") :Init("ST_HIDDEN", fsmContext) end -- ============================================================================ -- Private Helper Functions -- ============================================================================ function private.AuctionsGetItemText(row) if row:GetField("saleStatus") == 1 then return Theme.GetColor("INDICATOR"):ColorText(row:GetField("itemName")) else return TSM.UI.GetQualityColoredText(row:GetField("itemName"), row:GetField("itemQuality")) end end function private.AuctionsGetItemTooltip(itemString) return itemString ~= ItemString.GetPetCage() and itemString or nil end function private.AuctionsGetSoldIcon(self, data, iconIndex) assert(iconIndex == 1) local row = private.query:GetResultRowByUUID(data) if row:GetField("saleStatus") ~= 1 then return end return true, "iconPack.12x12/Bid", true end function private.AuctionsGetStackSizeText(row) if row:GetField("saleStatus") == 1 then return Theme.GetColor("INDICATOR"):ColorText(row:GetField("stackSize")) else return row:GetField("stackSize") end end function private.AuctionsGetTimeLeftText(row) local saleStatus, duration, isPending = row:GetFields("saleStatus", "duration", "isPending") if saleStatus == 0 and isPending then return "..." elseif saleStatus == 1 or not TSM.IsWowClassic() then local timeLeft = duration - time() local color = nil if saleStatus == 0 then if timeLeft <= 2 * SECONDS_PER_HOUR then color = Theme.GetFeedbackColor("RED") elseif timeLeft <= (TSM.IsWowClassic() and 8 or 24) * SECONDS_PER_HOUR then color = Theme.GetFeedbackColor("YELLOW") else color = Theme.GetFeedbackColor("GREEN") end else color = Theme.GetColor("INDICATOR") end local str = nil if timeLeft < SECONDS_PER_MIN then str = timeLeft.."s" elseif timeLeft < SECONDS_PER_HOUR then str = floor(timeLeft / SECONDS_PER_MIN).."m" else str = floor(timeLeft / SECONDS_PER_HOUR).."h" end return color and color:ColorText(str) or str else return TSM.UI.GetTimeLeftString(duration) end end function private.AuctionsGetHighBidderText(row) if row:GetField("saleStatus") == 1 then return Theme.GetColor("INDICATOR"):ColorText(row:GetField("highBidder")) else return row:GetField("highBidder") end end function private.AuctionsGetGroupNameText(row) local groupPath = row:GetField("group") local groupName = TSM.Groups.Path.GetName(groupPath) local level = select('#', strsplit(TSM.CONST.GROUP_SEP, groupPath)) return Theme.GetGroupColor(level):ColorText(groupName) end function private.AuctionsGetCurrentBidText(row) if row:GetField("saleStatus") == 1 then return Theme.GetColor("INDICATOR"):ColorText(L["Sold for:"]) elseif not TSM.IsWowClassic() and row:GetField("highBidder") ~= "" then return Money.ToString(row:GetField("currentBid"), Theme.GetColor("INDICATOR"):GetTextColorPrefix()) else return Money.ToString(row:GetField("currentBid")) end end function private.AuctionsGetCurrentBuyoutText(row) return Money.ToString(row:GetField(row:GetField("saleStatus") == 1 and "currentBid" or "buyout")) end