diff --git a/Bagshui.lua b/Bagshui.lua index 9c2b1ad..5d1902b 100644 --- a/Bagshui.lua +++ b/Bagshui.lua @@ -311,12 +311,13 @@ local bagshuiEnvironment = { bagType = "", charges = 0, count = 0, - equipLocationLocalized = "", equipLocation = "", - emptySlot = 0, -- Using 0/1 instead of true/false for easy sorting + equipLocationLocalized = "", + equipLocationSort = 0, + emptySlot = 0, -- Using 0/1 instead of true/false for easy sorting. id = 0, - itemLink = "", -- Item link (|cffffffff|Hitem:12345:0:0:0|h[Name]|h|r) - itemString = "", -- Item string (item:12345:0:0:0) + itemLink = "", -- Item link (|cffffffff|Hitem:12345:0:0:0|h[Name]|h|r). + itemString = "", -- Item string (item:12345:0:0:0). locked = 0, maxStackCount = 0, minLevel = "", @@ -330,7 +331,7 @@ local bagshuiEnvironment = { tooltip = "", texture = "Interface\\Icons\\INV_Misc_QuestionMark", type = "", - uncategorized = 0, -- Using 0/1 instead of true/false for easy sorting + uncategorized = 0, -- Using 0/1 instead of true/false for easy sorting. bagshuiGroupId = "", bagshuiCategoryId = "", @@ -357,8 +358,7 @@ local bagshuiEnvironment = { "bagType", "charges", "count", - "equipLocationLocalized", - "equipLocation", + "equipLocationSort", "emptySlot", "id", "itemLink", @@ -373,6 +373,22 @@ local bagshuiEnvironment = { "uncategorized", }, + -- ItemInfo:InitializeItem() will never reset these properties. + ---@type table + BS_ITEM_PROTECTED_PROPERTIES = { + bagNum = true, + bagType = true, + slotNum = true, + }, + + -- ItemInfo:InitializeItem() will not set these properties to default if nil. + -- Typically needed for properties that are used for empty slot identification. + ---@type table + BS_ITEM_NIL_PROPERTIES = { + itemLink = true, + itemString = true, + }, + -- Rule functions that correspond to item properties. -- This controls what appears in the Item Information menu, tooltip, and window. -- ``` @@ -486,6 +502,42 @@ local bagshuiEnvironment = { ---@type string[] BS_ITEM_PROPERTIES_SORTED = nil, + -- Used to get numbers for slot names so they can be sorted. + ---@type table + BS_INVENTORY_EQUIP_LOCATION_SORT_ORDER = { + INVTYPE_HEAD = 1, + INVTYPE_NECK = 2, + INVTYPE_SHOULDER = 3, + INVTYPE_CLOAK = 4, -- Back + INVTYPE_CHEST = 5, + INVTYPE_ROBE = 5, -- Chest + INVTYPE_BODY = 6, -- Shirt + INVTYPE_TABARD = 7, + INVTYPE_WRIST = 8, + INVTYPE_HAND = 9, + INVTYPE_WAIST = 10, + INVTYPE_LEGS = 11, + INVTYPE_FEET = 12, + INVTYPE_FINGER = 13, + INVTYPE_FINGER_OTHER = 14, + INVTYPE_TRINKET = 15, + INVTYPE_TRINKET_OTHER = 16, + INVTYPE_2HWEAPON = 17, -- Main Hand + INVTYPE_WEAPON = 17, -- Main Hand + INVTYPE_WEAPONMAINHAND = 17, -- Main Hand + INVTYPE_HOLDABLE = 18, -- Offhand + INVTYPE_SHIELD = 18, -- Offhand + INVTYPE_WEAPON_OTHER = 18, -- Offhand + INVTYPE_WEAPONOFFHAND = 18, -- Offhand + INVTYPE_RANGED = 19, + INVTYPE_RELIC = 19, -- Ranged + INVTYPE_WAND = 19, -- Ranged + INVTYPE_GUN = 19, -- Ranged + INVTYPE_CROSSBOW = 19, -- Ranged + INVTYPE_THROWN = 19, -- Ranged + INVTYPE_PROJECTILE = 20, -- Ammo + }, + -- In addition to the bagshuiStockState item property, stock state table values are used -- to determine the following stock badge attributes, so they must be PascalCase: @@ -955,6 +1007,21 @@ function Bagshui:AddonLoaded() end self.currentCharacterInfo = self.currentCharacterData[BS_CONFIG_KEY.CHARACTER_INFO] + -- Inventory validation. + -- This will ensure there are no errors when changes are made to the expected inventory cache structure. + for character, characterData in pairs(self.characters) do + for _, inventoryType in pairs(BS_INVENTORY_TYPE) do + inventoryType = string.lower(inventoryType) + if characterData[inventoryType] and characterData[inventoryType].inventory then + for containerNum, containerContents in pairs(characterData[inventoryType].inventory) do + for slotNum, item in ipairs(containerContents) do + BsItemInfo:InitializeItem(item, false, true) + end + end + end + end + end + -- Log storage. if _G.BagshuiData[BS_CONFIG_KEY.LOG] == nil then _G.BagshuiData[BS_CONFIG_KEY.LOG] = {} diff --git a/Bagshui.xml b/Bagshui.xml index 675bd58..4066c6b 100644 --- a/Bagshui.xml +++ b/Bagshui.xml @@ -20,6 +20,7 @@ Don't move things around unless you're sure it won't break anything. + @@ -68,9 +69,6 @@ Don't move things around unless you're sure it won't break anything. - - - diff --git a/Components/Inventory.Cache.lua b/Components/Inventory.Cache.lua index c559254..478d274 100644 --- a/Components/Inventory.Cache.lua +++ b/Components/Inventory.Cache.lua @@ -298,7 +298,7 @@ function Inventory:UpdateCache() else -- Slot is empty. - self:InitializeEmptySlotItem(item) + BsItemInfo:InitializeEmptySlotItem(item) if self.containers[bagNum].isProfessionBag then item.name = item.bagType .. " " .. item.name end @@ -598,32 +598,6 @@ end ---- Set the proper cache values for an empty slot item. ----@param item table self.inventory cache entry. ----@param isEmptySlotStack boolean? true for empty slot stack proxy entries. -function Inventory:InitializeEmptySlotItem(item, isEmptySlotStack) - item.id = 0 - -- Using 0/1 for ease of sorting. - item.emptySlot = 1 - -- Add "[Profession Bag Type]" to the name when needed. - item.name = - (isEmptySlotStack and item.bagType ~= BsGameInfo.itemSubclasses.Container.Bag) - and string.format(L.Suffix_EmptySlot, item.bagType) - or L.ItemPropFriendly_emptySlot - item.subtype = item.bagType - item.quality = -1 - item.charges = -1 - item.texture = nil - item.readable = nil - if not item._bagsRepresented then - item._bagsRepresented = {} - else - BsUtil.TableClear(item._bagsRepresented) - end -end - - - --- Prepare an entry in the emptySlotStacks table for to track the empty slot count for a given bag. ---@param bagInfo table self.containers entry. function Inventory:InitializeEmptySlotStackTracking(bagInfo) @@ -638,7 +612,7 @@ function Inventory:InitializeEmptySlotStackTracking(bagInfo) -- Always re-initialize when called to ensure bag changes don't result in incorrect empty slot textures. BsItemInfo:InitializeItem(self.emptySlotStacks[bagInfo.genericType]) self:AddItemBagInfo(self.emptySlotStacks[bagInfo.genericType], bagInfo, true) - self:InitializeEmptySlotItem(self.emptySlotStacks[bagInfo.genericType], true) + BsItemInfo:InitializeEmptySlotItem(self.emptySlotStacks[bagInfo.genericType], true) end diff --git a/Components/ItemInfo.lua b/Components/ItemInfo.lua index 3703dc4..21ed091 100644 --- a/Components/ItemInfo.lua +++ b/Components/ItemInfo.lua @@ -4,7 +4,10 @@ -- Provide PeriodicTable interface. -- Display a window with an edit box to provide copyable item information. -Bagshui:AddComponent(function() + +-- Using `LoadComponent()` to get `BsItemInfo` into the environment immediately so +-- `Bagshui:AddonLoaded()` can access `InitializeItem()` for inventory cache validation. +Bagshui:LoadComponent(function() Bagshui:AddConstants({ @@ -25,14 +28,6 @@ Bagshui:AddConstants({ TOOLTIP = "TOOLTIP", }, - -- ItemInfo:InitializeItem() will never reset these properties. - ---@type table - BS_ITEM_INFO_PROTECTED_PROPERTIES = { - bagNum = true, - bagType = true, - slotNum = true, - }, - }) @@ -41,25 +36,24 @@ local ItemInfo = { periodicTableSetCache = {}, periodicTableSetCacheItemLink = "_", -- Not using "" so that empty slots don't match. - -- This is the ItemInfo window. - window = Bagshui.prototypes.ScrollableTextWindow:New({ - name = "ItemInfo", - title = "Item Info", -- Will be updated to the name of the item in Open(). - readOnly = true, - width = 400, - height = 650, - selectAllOnFocus = false, - }) + -- This is the ItemInfo window, declared in `ItemInfo:Init()`. + window = nil } Bagshui.components.ItemInfo = ItemInfo Bagshui.environment.BsItemInfo = ItemInfo --- local infoWindowName = "ItemInfo" --- local ui, uiFrame, uiTitle, scrollFrame, infoBox, itemSlotButton - --- Given an item link or string, obtain as much information as possible. +--- # Important best practice for this function! +--- Except in special cases, provide fallback values from `BS_ITEM_SKELETON`: +--- ``` +--- item.name = newName or BS_ITEM_SKELETON.name +--- ``` +--- This makes it safe for other code to assume that item property values won't be nil. +--- Notable special cases, both used for empty slot differentiation: +--- * `itemString` +--- * `itemLink` ---@param itemIdentifier string|number|nil Item ID, link, or string. Allowed to be nil for empty slots. ---@param itemInfoTable table Table that will be filled with item information. ---@param initialize boolean? When true, always wipe the provided `itemInfoTable` before doing anything else. @@ -147,10 +141,10 @@ function ItemInfo:Get(itemIdentifier, itemInfoTable, reinitialize, initialize, f -- Add item information that will be applicable regardless of whether this is -- an empty slot (or nil'd if it's an empty slot). - item.itemString = itemString - item.id = itemId - item.texture = itemTexture - item.bagshuiInventoryType = (inventory and inventory.inventoryType or nil) -- Item location (Bags, Bank, etc). + item.itemString = itemString -- Must be allowed to be nil for empty slot identification. + item.id = itemId or BS_ITEM_SKELETON.id + item.texture = itemTexture or BS_ITEM_SKELETON.texture + item.bagshuiInventoryType = (inventory and inventory.inventoryType or BS_ITEM_SKELETON.bagshuiInventoryType) -- Item location (Bags, Bank, etc). -- itemStringGeneric is disabled -- see BS_ITEM_SKELETON in Bagshui.lua for details. -- To restore this, bring back: @@ -163,13 +157,16 @@ function ItemInfo:Get(itemIdentifier, itemInfoTable, reinitialize, initialize, f -- GetItemInfo() results. - item.name = itemName - item.quality = itemQuality - item.minLevel = itemMinLevel - item.type = itemType - item.subtype = itemSubtype - item.maxStackCount = itemMaxStackCount - item.equipLocation = itemEquipLocation + item.name = itemName or BS_ITEM_SKELETON.name + -- EngInventory/EngBags had an "unknown" quality value of -1 but that doesn't + -- seem likely to occur (or particularly relevant to convey?), so let's just + -- use the default quality color if we somehow didn't get one. + item.quality = itemQuality or BS_ITEM_SKELETON.quality + item.minLevel = itemMinLevel or BS_ITEM_SKELETON.minLevel + item.type = itemType or BS_ITEM_SKELETON.type + item.subtype = itemSubtype or BS_ITEM_SKELETON.subtype + item.maxStackCount = itemMaxStackCount or BS_ITEM_SKELETON.maxStackCount + item.equipLocation = itemEquipLocation or BS_ITEM_SKELETON.equipLocation -- Apply any property overrides from Config\ItemFixes.lua. if Bagshui.config.ItemFixes[item.id] then @@ -182,9 +179,11 @@ function ItemInfo:Get(itemIdentifier, itemInfoTable, reinitialize, initialize, f -- Store localized versions of information. if item.equipLocation and string.len(item.equipLocation) > 0 then - item.equipLocationLocalized = _G[item.equipLocation] + item.equipLocationLocalized = _G[item.equipLocation] or BS_ITEM_SKELETON.equipLocationLocalized + -- This is also the best place to add the sortable version of equipLocation. + item.equipLocationSort = BS_INVENTORY_EQUIP_LOCATION_SORT_ORDER[item.equipLocation] or BS_ITEM_SKELETON.equipLocationSort end - item.qualityLocalized = _G[string.format("ITEM_QUALITY%d_DESC", item.quality)] + item.qualityLocalized = _G[string.format("ITEM_QUALITY%d_DESC", item.quality)] or BS_ITEM_SKELETON.qualityLocalized -- If this is an item with a random suffix, figure out what it is. -- This will be used for reversed name sorting so that "Amazing Helmet of the Bear" @@ -193,8 +192,8 @@ function ItemInfo:Get(itemIdentifier, itemInfoTable, reinitialize, initialize, f itemBaseName = _G.GetItemInfo(itemLinkWithoutRandomSuffix) itemSuffixName = BsUtil.Trim(string.gsub(itemName, itemBaseName, "")) end - item.baseName = itemBaseName - item.suffixName = itemSuffixName + item.baseName = itemBaseName or BS_ITEM_SKELETON.baseName + item.suffixName = itemSuffixName or BS_ITEM_SKELETON.suffixName -- Load tooltip. self:GetTooltip(item, inventory) @@ -202,6 +201,7 @@ function ItemInfo:Get(itemIdentifier, itemInfoTable, reinitialize, initialize, f end -- itemString exists -- Construct an item link if the itemIdentifier wasn't one. + -- itemLink must be allowed to be nil for empty slot identification. if itemString ~= nil and not string.find(tostring(itemIdentifier), "^|") then local itemQualityColor = _G.ITEM_QUALITY_COLORS[item.quality or 1].hex item.itemLink = itemQualityColor .. "|H" .. itemString .. "|H[" .. (itemName or L.Unknown) .. "]|H|r" @@ -209,14 +209,6 @@ function ItemInfo:Get(itemIdentifier, itemInfoTable, reinitialize, initialize, f item.itemLink = itemIdentifier end - -- Make sure important values aren't nil to avoid errors elsewhere. - item.name = item.name or BS_ITEM_SKELETON.name - item.subtype = item.subtype or BS_ITEM_SKELETON.subtype - -- EngInventory/EngBags had an "unknown" quality value of -1 but that doesn't - -- seem likely to occur (or particularly relevant to convey?), so let's just - -- use the default quality color if we somehow didn't get one. - item.quality = item.quality or BS_ITEM_SKELETON.quality - return true end @@ -224,19 +216,27 @@ end --- Set all values of an inventory cache item to default. --- It's up to calling functions to manage any of the properties in ---- BS_ITEM_INFO_PROTECTED_PROPERTIES when they already have values. +--- BS_ITEM_PROTECTED_PROPERTIES when they already have values. ---@param itemInfoTable table Table that will be filled with item information. ----@param initializeProtected boolean? Reset properties protected by `BS_ITEM_INFO_PROTECTED_PROPERTIES`. -function ItemInfo:InitializeItem(itemInfoTable, initializeProtected) +---@param initializeProtected boolean? Reset properties protected by `BS_ITEM_PROTECTED_PROPERTIES`. +---@param validate boolean? Never overwrite anything. In this mode, missing properties are filled and anything not in `BS_ITEM_SKELETON` is removed. +function ItemInfo:InitializeItem(itemInfoTable, initializeProtected, validate, temp) + -- Existing property resets/missing property filling. for itemKey, defaultValue in pairs(BS_ITEM_SKELETON) do - -- Never reset a protected property; only fill it if it's empty. - -- All other properties are fair game. if - not ( - BS_ITEM_INFO_PROTECTED_PROPERTIES[itemKey] - and not initializeProtected + ( + itemInfoTable[itemKey] == nil + and not BS_ITEM_NIL_PROPERTIES[itemKey] + ) + or ( + -- Don't reset properties with values during validation. + not validate + -- Never reset a protected property unless forced. + and not ( + BS_ITEM_PROTECTED_PROPERTIES[itemKey] + and not initializeProtected + ) ) - or not itemInfoTable[itemKey] then if type(defaultValue) == "table" then itemInfoTable[itemKey] = BsUtil.TableCopy(defaultValue) @@ -245,10 +245,44 @@ function ItemInfo:InitializeItem(itemInfoTable, initializeProtected) end end end + + -- Validation only: remove extraneous properties. + if validate then + for itemKey in next, itemInfoTable do + if BS_ITEM_SKELETON[itemKey] == nil then + rawset(itemInfoTable, itemKey, nil) + end + end + end end +--- Set the proper cache values for an empty slot item. +---@param item table Filled by `ItemInfo:Get()`. +---@param isEmptySlotStack boolean? true for empty slot stack proxy entries. +function ItemInfo:InitializeEmptySlotItem(item, isEmptySlotStack) + item.id = 0 + -- Using 0/1 for ease of sorting. + item.emptySlot = 1 + -- Add "[Profession Bag Type]" to the name when needed. + item.name = + (isEmptySlotStack and item.bagType ~= BsGameInfo.itemSubclasses.Container.Bag) + and string.format(L.Suffix_EmptySlot, item.bagType) + or L.ItemPropFriendly_emptySlot + item.subtype = item.bagType + item.quality = -1 + item.charges = -1 + item.texture = nil + item.readable = nil + if not item._bagsRepresented then + item._bagsRepresented = {} + else + BsUtil.TableClear(item._bagsRepresented) + end +end + + --- `GetItemInfo()` in Vanilla doesn't accept the full item links returned by `GetContainerItemLink()`, --- so we need to parse out just the `item:itemId:enchantId:suffixId:uniqueId` part. @@ -751,94 +785,128 @@ end ---- ScrollableTextWindow override for item info display. ----@param item table Item information table populated by ItemInfo:Get(). -function ItemInfo.window:Open(item) - -- Nothing to do. - if not item then - return - end - -- Build item information text. +--- Just need to do this little bit after initialization because the UI classes +--- aren't loaded until well after ItemInfo. +function ItemInfo:Init() + self.window = Bagshui.prototypes.ScrollableTextWindow:New({ + name = "ItemInfo", + title = "Item Info", -- Will be updated to the name of the item in Open(). + readOnly = true, + width = 400, + height = 650, + selectAllOnFocus = false, + }) + + + --- ScrollableTextWindow override for item info display. + ---@param item table Item information table populated by ItemInfo:Get(). + function self.window:Open(item) + -- Nothing to do. + if not item then + return + end + + -- Build item information text. - local infoText = "" + local infoText = "" - for itemProperty, itemPropertyFriendly, itemPropertyValue, itemPropertyDisplay in ItemInfo:ItemPropertyValuesForDisplay(item, BS_ITEM_INFO_DISPLAY_TYPE.TEXT) do + for itemProperty, itemPropertyFriendly, itemPropertyValue, itemPropertyDisplay in ItemInfo:ItemPropertyValuesForDisplay(item, BS_ITEM_INFO_DISPLAY_TYPE.TEXT) do - -- Start this property with `: `. - infoText = infoText .. string.format(L.Symbol_Colon, itemPropertyFriendly) .. " " .. itemPropertyDisplay .. BS_NEWLINE + -- Start this property with `: `. + infoText = infoText .. string.format(L.Symbol_Colon, itemPropertyFriendly) .. " " .. itemPropertyDisplay .. BS_NEWLINE - -- Add rule function examples. - local ruleFunctionExamples = BS_ITEM_PROPERTIES_TO_FUNCTIONS[itemProperty] - if type(ruleFunctionExamples) == "table" then - if type(itemPropertyValue) == "table" then - for _, val in ipairs(itemPropertyValue) do - infoText = infoText .. ItemInfo:BuildRuleFunctionExampleText(ruleFunctionExamples, val, itemPropertyFriendly) + -- Add rule function examples. + local ruleFunctionExamples = BS_ITEM_PROPERTIES_TO_FUNCTIONS[itemProperty] + if type(ruleFunctionExamples) == "table" then + if type(itemPropertyValue) == "table" then + for _, val in ipairs(itemPropertyValue) do + infoText = infoText .. ItemInfo:BuildRuleFunctionExampleText(ruleFunctionExamples, val, itemPropertyFriendly) + end + else + infoText = infoText .. ItemInfo:BuildRuleFunctionExampleText(ruleFunctionExamples, itemPropertyValue, itemPropertyFriendly) end else - infoText = infoText .. ItemInfo:BuildRuleFunctionExampleText(ruleFunctionExamples, itemPropertyValue, itemPropertyFriendly) + infoText = infoText .. GRAY_FONT_COLOR_CODE .. L.NoRuleFunction .. FONT_COLOR_CODE_CLOSE .. BS_NEWLINE end - else - infoText = infoText .. GRAY_FONT_COLOR_CODE .. L.NoRuleFunction .. FONT_COLOR_CODE_CLOSE .. BS_NEWLINE + + -- End with a final newline so that there are two newlines to separate from + -- the next property (extra newlines at the very end don't matter). + infoText = infoText .. BS_NEWLINE + end - -- End with a final newline so that there are two newlines to separate from - -- the next property (extra newlines at the very end don't matter). - infoText = infoText .. BS_NEWLINE + -- Prepare to open the window. + if self._super.Open(self, infoText, true) == false then + return + end - end + -- Set title to item name. + self.uiTitle:SetText( + string.format(L.Symbol_Colon, L.BagshuiItemInformation) .. " " + .. HIGHLIGHT_FONT_COLOR_CODE .. tostring(item.name) .. FONT_COLOR_CODE_CLOSE + ) - -- Prepare to open the window. - if self._super.Open(self, infoText, true) == false then - return + -- Set item slot button in top left to item. + self.ui:AssignItemToItemButton(self.itemSlotButton, item) + self.itemSlotButton.bagshuiData.itemString = item.itemString -- This will be picked up for the tooltip. + + -- Actually open the window. + -- self:Open() end - -- Set title to item name. - self.uiTitle:SetText( - string.format(L.Symbol_Colon, L.BagshuiItemInformation) .. " " - .. HIGHLIGHT_FONT_COLOR_CODE .. tostring(item.name) .. FONT_COLOR_CODE_CLOSE - ) - -- Set item slot button in top left to item. - self.ui:AssignItemToItemButton(self.itemSlotButton, item) - self.itemSlotButton.bagshuiData.itemString = item.itemString -- This will be picked up for the tooltip. - -- Actually open the window. - -- self:Open() -end + --- ScrollableTextWindow override for initializing UI. + function self.window:InitUi() + if not self then + return + end + -- Calls ScrollableTextWindow:InitUi(). + if self._super.InitUi(self) == false then + return + end + -- Add item slot button to top left of window. + self.itemSlotButton = self.ui:CreateItemSlotButton("ItemSlotButton", self.uiHeader) + self.itemSlotButton.bagshuiData.tooltipAnchorDefault = true + self.itemSlotButton.bagshuiData.noBorderScale = true + self.ui:SetItemButtonSize(self.itemSlotButton, self.uiTitle:GetHeight() + 2) + self.itemSlotButton:SetPoint("LEFT", self.uiFrame.bagshuiData.header) + -- Remove clickability and pass drag events to the window. + self.itemSlotButton:RegisterForClicks(nil) + self.itemSlotButton:RegisterForDrag("LeftButton") + self.itemSlotButton:SetScript("OnDragStart", function() + if _G.GameTooltip:IsOwned(_G.this) then + _G.GameTooltip:Hide() + end + self.uiFrame:StartMoving() + end) + self.itemSlotButton:SetScript("OnDragStop", function() + self.uiFrame:StopMovingOrSizing() + end) ---- ScrollableTextWindow override for initializing UI. -function ItemInfo.window:InitUi() - -- Calls ScrollableTextWindow:InitUi(). - if self._super.InitUi(self) == false then - return + -- Anchor title to item slot button. + self.uiTitle:ClearAllPoints() + self.uiTitle:SetPoint("LEFT", self.itemSlotButton, "RIGHT", 4, 0) end - -- Add item slot button to top left of window. - self.itemSlotButton = self.ui:CreateItemSlotButton("ItemSlotButton", self.uiHeader) - self.itemSlotButton.bagshuiData.tooltipAnchorDefault = true - self.itemSlotButton.bagshuiData.noBorderScale = true - self.ui:SetItemButtonSize(self.itemSlotButton, self.uiTitle:GetHeight() + 2) - self.itemSlotButton:SetPoint("LEFT", self.uiFrame.bagshuiData.header) - -- Remove clickability and pass drag events to the window. - self.itemSlotButton:RegisterForClicks(nil) - self.itemSlotButton:RegisterForDrag("LeftButton") - self.itemSlotButton:SetScript("OnDragStart", function() - if _G.GameTooltip:IsOwned(_G.this) then - _G.GameTooltip:Hide() - end - self.uiFrame:StartMoving() - end) - self.itemSlotButton:SetScript("OnDragStop", function() - self.uiFrame:StopMovingOrSizing() - end) - - -- Anchor title to item slot button. - self.uiTitle:ClearAllPoints() - self.uiTitle:SetPoint("LEFT", self.itemSlotButton, "RIGHT", 4, 0) end + +--- Event handling. +---@param event string Event identifier. +---@param arg1 any? Argument 1 from the event. +function ItemInfo:OnEvent(event, arg1) + if event == "ADDON_LOADED" then + self:Init() + end +end + + +Bagshui:RegisterEvent("ADDON_LOADED", ItemInfo) + + end) \ No newline at end of file diff --git a/Components/SortOrders.lua b/Components/SortOrders.lua index badbbee..09ab6fd 100644 --- a/Components/SortOrders.lua +++ b/Components/SortOrders.lua @@ -103,6 +103,10 @@ function SortOrders:Init() self._super.Init(self) + -- Ensure no invalid fields in Sort Orders. + self:CleanSortOrders() + + -- Link the list of sort orders to the defaultSortOrder setting's list of valid choices. -- This will let the Settings class know which sort orders are valid. Bagshui.prototypes.Settings._settingInfo.defaultSortOrder.choices = self.list @@ -140,9 +144,14 @@ function SortOrders:Init() -- slotNum ASC local tooltipText for _, sort in pairs(self.list[id].fields) do + local sortField = self:GetFieldIdentifier(sort.field, sort.lookup, "::") + local sortFieldFriendly = + self.sortFields[sortField] + and self.sortFields[sortField].friendlyName + or (sort.field .. (sort.lookup and ("." .. sort.lookup) or "")) tooltipText = (tooltipText and tooltipText .. BS_NEWLINE or "") - .. self.sortFields[self:GetFieldIdentifier(sort.field, sort.lookup, "::")].friendlyName + .. sortFieldFriendly if sort.reverseWords then tooltipText = string.format(L.Suffix_Reversed, tooltipText) end @@ -258,26 +267,33 @@ end --- Test whether the given sort order is valid. ---- Throws an error if it's invalid. +--- Throws an error if it's invalid (unless `removeIfInvalid` passed). ---@param sortOrderId string|number ---@param sortOrderInfo table Sort order object to validate. +---@param removeIfInvalid boolean? Remove the sort field instead of throwing an error when it's invalid. ---@return boolean -function SortOrders:ValidateSortOrder(sortOrderId, sortOrderInfo) +function SortOrders:ValidateSortOrder(sortOrderId, sortOrderInfo, removeIfInvalid) assert(sortOrderId, "sortOrderId is required to perform validation") local baseError = "Sort order " .. sortOrderId .. " failed validation: " assert(sortOrderInfo.name, baseError .. "name is missing") assert(sortOrderInfo.fields, baseError .. "fields is missing") - for _, sort in pairs(sortOrderInfo.fields) do + -- Walk backwards so we can use table.remove(). + for index = table.getn(sortOrderInfo.fields), 1, -1 do + local sort = sortOrderInfo.fields[index] local fieldId = self:GetFieldIdentifier(sort.field, sort.lookup) - assert(self.sortFields[fieldId], baseError .. self:GetFieldIdentifier(sort.field, sort.lookup, ".") .. " is not a valid sort field") - - -- Ensure a valid value for asc/desc. - if BsUtil.Trim(string.lower(sort.direction or "")) == "desc" then - sort.direction = "desc" - else - sort.direction = "asc" + if not self.sortFields[fieldId] then + if removeIfInvalid then + table.remove(sortOrderInfo.fields, index) + else + -- This should really never be hit if everything else is handled + -- well, so there's no need to make it pretty. It's really more + -- for the built-in sort orders than anything. + assert(false, baseError .. self:GetFieldIdentifier(sort.field, sort.lookup, ".") .. " is not a valid sort field") + end end + + self:ValidateSortFieldDirection(sort) end return true @@ -285,6 +301,26 @@ end +--- Set the sort direction of a field to "asc" if it's not currently "desc". +---@param sortFieldTable table A table from the list of sort fields within a sort order. Should typically have `field` and `direction` keys. +function SortOrders:ValidateSortFieldDirection(sortFieldTable) + sortFieldTable.direction = BsUtil.Trim(string.lower(sortFieldTable.direction or "")) == "desc" and "desc" or "asc" +end + + + +--- Loop through all Sort Orders and make sure they're good before trying to use them. +--- This basically just removes any invalid sort fields (anything not in +--- BS_ITEM_PROPERTIES_ALLOWED_IN_SORT_ORDERS), so be sure to take care of any migration +--- by updating Config\SortOrders.lua first. +function SortOrders:CleanSortOrders() + for id, sortOrderInfo in pairs(self.list) do + self:ValidateSortOrder(id, sortOrderInfo, true) + end +end + + + --- Helper function: Obtain either the normal or "Sort" version of a lookup table item property. ---@param lookupObject table Object to pull property values from. ---@param lookupProperty string Property to look up. diff --git a/Config/SortOrders.lua b/Config/SortOrders.lua index 362de3d..bddc3a4 100644 --- a/Config/SortOrders.lua +++ b/Config/SortOrders.lua @@ -8,7 +8,7 @@ Bagshui:AddComponent(function() -- but each one *must* have a unique `id` property that will become its object ID. ---@type table[] Bagshui.config.SortOrders = { - version = 1, + version = 2, defaults = { -- Initially selected default sort order. @@ -19,6 +19,7 @@ Bagshui.config.SortOrders = { { field = "emptySlot", direction = "desc", }, { field = "uncategorized", direction = "asc", }, { lookup = "Category", field = "name", direction = "asc", }, + { field = "equipLocationSort", direction = "asc", }, { field = "type", direction = "asc", }, { field = "subtype", direction = "asc", }, { field = "quality", direction = "desc", }, @@ -38,6 +39,7 @@ Bagshui.config.SortOrders = { { field = "emptySlot", direction = "desc", }, { field = "uncategorized", direction = "asc", }, { lookup = "Category", field = "name", direction = "asc", }, + { field = "equipLocationSort", direction = "asc", }, { field = "type", direction = "asc", }, { field = "subtype", direction = "asc", }, { field = "quality", direction = "desc", }, @@ -57,6 +59,7 @@ Bagshui.config.SortOrders = { { field = "emptySlot", direction = "desc", }, { field = "uncategorized", direction = "asc", }, { lookup = "Category", field = "name", direction = "asc", }, + { field = "equipLocationSort", direction = "asc", }, { field = "type", direction = "asc", }, { field = "subtype", direction = "asc", }, { field = "minLevel", direction = "desc", }, @@ -77,6 +80,7 @@ Bagshui.config.SortOrders = { { field = "emptySlot", direction = "desc", }, { field = "uncategorized", direction = "asc", }, { lookup = "Category", field = "name", direction = "asc", }, + { field = "equipLocationSort", direction = "asc", }, { field = "type", direction = "asc", }, { field = "subtype", direction = "asc", }, { field = "minLevel", direction = "desc", }, @@ -102,7 +106,34 @@ Bagshui.config.SortOrders = { -- Currently no need for migration. - migrate = nil, + migrate = function(sortOrders, oldVersion) + + -- The only equipLocation field allowed in Sort Orders from v2 on is equipLocationSort. + if oldVersion < 2 then + for id, sortOrder in pairs(sortOrders.list) do + if type(sortOrder.fields) == "table" then + local lastEquipLocationIndex + -- Walk backwards so we can use table.remove(). + for index = table.getn(sortOrder.fields), 1, -1 do + if + type(sortOrder.fields[index].field) == "string" + and string.find(sortOrder.fields[index].field, "^equipLocation") + then + -- Prune down to just one equipLocation field. + if lastEquipLocationIndex then + table.remove(sortOrder.fields, lastEquipLocationIndex) + end + sortOrder.fields[index].field = "equipLocationSort" + -- Being slightly presumptuous, it seems like most people would prefer + -- character sheet order from top to bottom rather than vice-versa. + sortOrder.fields[index].direction = "asc" + lastEquipLocationIndex = index + end + end + end + end + end + end, } diff --git a/Locale/enUS.lua b/Locale/enUS.lua index 58226da..6b24160 100644 --- a/Locale/enUS.lua +++ b/Locale/enUS.lua @@ -231,6 +231,7 @@ BsLocalization:AddLocale("enUS", { ["ItemPropFriendly_count"] = "Count", ["ItemPropFriendly_equipLocation"] = "Equip Location", ["ItemPropFriendly_equipLocationLocalized"] = "Equip Location (Localized)", +["ItemPropFriendly_equipLocationSort"] = "Equip Location", ["ItemPropFriendly_emptySlot"] = "Empty Slot", ["ItemPropFriendly_id"] = "Item ID", ["ItemPropFriendly_itemLink"] = "Item Link",