enUS Localization Update Announcement / localization-update-announcement (push) Canceled after 0s
411 lines
17 KiB
Lua
411 lines
17 KiB
Lua
-- Bagshui Active Sort Order Management
|
|
-- Exposes: BsSortOrders (and Bagshui.components.SortOrders)
|
|
-- Raises: BAGSHUI_SORTORDER_UPDATE
|
|
|
|
Bagshui:AddComponent(function()
|
|
Bagshui:AddConstants({
|
|
|
|
-- Allowed properties and default values.
|
|
BS_SORT_ORDER_SKELETON = {
|
|
name = "",
|
|
fields = {
|
|
-- This permits an array of tables containing:
|
|
{
|
|
lookup = "",
|
|
field = "",
|
|
direction = "asc",
|
|
reverseWords = false,
|
|
},
|
|
},
|
|
},
|
|
|
|
-- Template for creating a new sort order.
|
|
BS_NEW_SORT_ORDER_TEMPLATE = {
|
|
name = "",
|
|
fields = {},
|
|
},
|
|
})
|
|
|
|
-- Key in _G.BagshuiData where sort orders will be stored.
|
|
local SORT_ORDERS_DATA_STORAGE_KEY = "sortOrders"
|
|
|
|
-- SortOrders class is built on the ObjectList prototype.
|
|
local SortOrders = Bagshui.prototypes.ObjectList:New({
|
|
|
|
dataStorageKey = SORT_ORDERS_DATA_STORAGE_KEY,
|
|
objectVersion = Bagshui.config.SortOrders.version,
|
|
objectMigrationFunction = Bagshui.config.SortOrders.migrate,
|
|
objectName = "SortOrder",
|
|
objectNamePlural = "SortOrders",
|
|
objectSkeleton = BS_SORT_ORDER_SKELETON,
|
|
objectTemplate = BS_NEW_SORT_ORDER_TEMPLATE,
|
|
defaults = Bagshui.config.SortOrders.defaults,
|
|
wikiPage = BS_WIKI_PAGES.SortOrders,
|
|
|
|
-- Can't initialize until Categories are done.
|
|
initEvent = "BAGSHUI_CATEGORIES_LOADED",
|
|
|
|
-- Lists of valid item fields, populated by AddValidItemFields.
|
|
validItemFields = {},
|
|
sortFields = {},
|
|
sortFieldsSortedByName = {},
|
|
|
|
-- Properties that need to be looked up in a table that isn't the item.
|
|
-- ```
|
|
-- <Lookup Name> = {
|
|
-- property = "",
|
|
-- table = <Lookup Table>,
|
|
-- }
|
|
-- ```
|
|
-- Populated by AddValidItemFields().
|
|
itemFieldLookups = {},
|
|
|
|
-- Debug.
|
|
debugResetOnLoad = false and BS_DEBUG,
|
|
})
|
|
|
|
Bagshui.environment.BsSortOrders = SortOrders
|
|
Bagshui.components.SortOrders = SortOrders
|
|
|
|
--- Initialization of Sort Orders depends on having the Categories class initialized
|
|
--- because of BS_CATEGORY_PROPERTIES_ALLOWED_IN_SORT_ORDERS. To deal with this,
|
|
--- Categories raises BAGSHUI_CATEGORIES_LOADED and we initialize based on that event.
|
|
function SortOrders:Init()
|
|
-- Build list of field names that are valid for use in sort orders.
|
|
|
|
-- Item properties.
|
|
self:AddValidItemFields(BS_ITEM_PROPERTIES_ALLOWED_IN_SORT_ORDERS, BS_ITEM_SKELETON)
|
|
|
|
-- Category properties as `Category.<property>`.
|
|
self:AddValidItemFields(
|
|
BS_CATEGORY_PROPERTIES_ALLOWED_IN_SORT_ORDERS,
|
|
BS_CATEGORY_SKELETON,
|
|
"Category",
|
|
"bagshuiCategoryId",
|
|
BsCategories.list
|
|
)
|
|
|
|
-- Load default sort orders and do other prototype class initialization.
|
|
-- IMPORTANT: This can't happen until after valid field lists are built.
|
|
-- Calls ObjectList: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
|
|
|
|
-- Add the auto-split menus.
|
|
|
|
-- Sort Orders (used in Settings and Group menus).
|
|
Bagshui.prototypes.Menus:AddAutoSplitMenu(BS_AUTO_SPLIT_MENU_TYPE.SORT_ORDERS, {
|
|
defaultIdList = self.sortedIdLists.name,
|
|
sortFunc = function(sortOrderIds)
|
|
self:SortIdList(sortOrderIds)
|
|
end,
|
|
nameFunc = function(id)
|
|
return self:GetName(id) or tostring(id)
|
|
end,
|
|
tooltipTitleFunc = function()
|
|
-- Since sort order names can be long, just display "Details:" as the tooltip title.
|
|
return string.format(L.Symbol_Colon, L.Details)
|
|
end,
|
|
tooltipTextFunc = function(id, originalTooltipText)
|
|
if not id then
|
|
return originalTooltipText
|
|
end
|
|
-- Create a tooltip with one line per field, formatted like:
|
|
-- Category Name ASC
|
|
-- Type ASC
|
|
-- Subtype ASC
|
|
-- Quality DESC
|
|
-- Name [reversed] ASC
|
|
-- count DESC
|
|
-- bagNum ASC
|
|
-- 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 "") .. sortFieldFriendly
|
|
if sort.reverseWords then
|
|
tooltipText = string.format(L.Suffix_Reversed, tooltipText)
|
|
end
|
|
tooltipText = tooltipText
|
|
.. " "
|
|
.. LIGHTYELLOW_FONT_COLOR_CODE
|
|
.. string.lower(sort.direction)
|
|
.. FONT_COLOR_CODE_CLOSE
|
|
end
|
|
return tooltipText
|
|
end,
|
|
extraItems = {
|
|
{
|
|
text = string.format(L.Symbol_Ellipsis, L.Manage),
|
|
tooltipTitle = L.Manage,
|
|
tooltipText = string.format(L.Prefix_Manage, L.SortOrders),
|
|
checked = false,
|
|
value = {
|
|
func = function()
|
|
Bagshui:CloseMenus()
|
|
self:Open()
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
-- Sort Fields (used by Sort Order editor).
|
|
Bagshui.prototypes.Menus:AddAutoSplitMenu(BS_AUTO_SPLIT_MENU_TYPE.SORT_FIELDS, {
|
|
defaultIdList = self.sortFieldsSortedByName,
|
|
sortFunc = function(sortFieldIds)
|
|
-- Don't need to do anything here - these don't ever change after startup.
|
|
end,
|
|
nameFunc = function(id, menuValue)
|
|
return self.sortFields[id].friendlyName
|
|
end,
|
|
})
|
|
end
|
|
|
|
-- Using a list of valid fields and an object template (table of `<fieldName> = <defaultValue>`), populate the validItemFields table.
|
|
---@param validFieldList string[] Array of fields that are allowed in sorting.
|
|
---@param objectTemplate table<string,string> List of `{ [fieldName] = [defaultValue] }`, used to figure out field type.
|
|
---@param lookupObjectName string? Name of the "lookup object" (for example, "Category").
|
|
---@param lookupProperty string? When lookup = lookupObjectName is encountered in a sort order, what item property should be used to find the lookup object (i.e. bagshuiCategoryId).
|
|
---@param lookupTable table? Where can the object identified by lookupObjectProperty be found? (for example, BsCategories.list).
|
|
function SortOrders:AddValidItemFields(validFieldList, objectTemplate, lookupObjectName, lookupProperty, lookupTable)
|
|
assert(
|
|
not lookupObjectName or (lookupObjectName and lookupProperty and lookupTable),
|
|
"SortOrders:AddValidItemFields(): All lookup parameters are required when one is provided"
|
|
)
|
|
|
|
-- This is a lookup object, so store lookup information.
|
|
if string.len(lookupObjectName or "") > 0 then
|
|
self.itemFieldLookups[lookupObjectName] = {
|
|
property = lookupProperty,
|
|
table = lookupTable,
|
|
}
|
|
end
|
|
|
|
-- Iterate the list of fields and save information.
|
|
for _, fieldName in pairs(validFieldList) do
|
|
-- Add to the lists of available fields.
|
|
local uniqueName = self:GetFieldIdentifier(fieldName, lookupObjectName)
|
|
self.sortFields[uniqueName] = {
|
|
friendlyName = self:GetFieldIdentifier(L["ItemPropFriendly_" .. fieldName], lookupObjectName, " "),
|
|
property = fieldName,
|
|
type = type(objectTemplate[fieldName]),
|
|
allowReverseWords = (fieldName == "name"), -- Hardcoding this as I can't currently think of any scenario where anything other than item name would makes sense for reversed words.
|
|
lookup = lookupObjectName,
|
|
lookupProperty = lookupProperty,
|
|
lookupTable = lookupTable,
|
|
}
|
|
|
|
table.insert(self.sortFieldsSortedByName, uniqueName)
|
|
end
|
|
|
|
table.sort(self.sortFieldsSortedByName)
|
|
end
|
|
|
|
-- Generate a unique ID for fields and to create their friendly name (when a space is passed in for separator).
|
|
---@param fieldName string Name of the field.
|
|
---@param lookupObjectName string? Name of the "lookup object" (for example, "Category"), if any.
|
|
---@param separator string? String to use between lookup object name and field name.
|
|
---@return string fieldIdentifier [lookupObjectName][separator][fieldName]
|
|
function SortOrders:GetFieldIdentifier(fieldName, lookupObjectName, separator)
|
|
return (lookupObjectName and (lookupObjectName .. (separator or "::")) or "") .. fieldName
|
|
end
|
|
|
|
--- See `ObjectList:GetUses()`.
|
|
---@param objectId string|number
|
|
---@param objectMetadata table Metadata about the object.
|
|
---@return boolean inUse Whether the object is being used.
|
|
function SortOrders:GetUses(objectId, objectMetadata)
|
|
return BsProfiles:GetProfilesUsingObject(objectId, objectMetadata, BsSortOrders)
|
|
end
|
|
|
|
-- Ensure the sort order is valid before saving.
|
|
---@param sortOrderId string|number ID of the sort order being saved.
|
|
---@param sortOrderInfo table Sort order object to be saved.
|
|
function SortOrders:DoPreSaveOperations(sortOrderId, sortOrderInfo)
|
|
if not self:ValidateSortOrder(sortOrderId, sortOrderInfo) then
|
|
return
|
|
end
|
|
end
|
|
|
|
--- Test whether the given sort order is valid.
|
|
--- Throws an error if it's invalid (unless `removeIfInvalid` passed).
|
|
---@param sortOrderId string|number
|
|
---@param sortOrderInfo table<string,any> 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, 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")
|
|
|
|
-- 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)
|
|
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
|
|
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.
|
|
---@return any propertyValue Property value.
|
|
local function SortGroup_GetLookupFieldProperty(lookupObject, lookupProperty)
|
|
if lookupObject[lookupProperty .. "Sort"] then
|
|
return lookupObject[lookupProperty .. "Sort"]
|
|
end
|
|
return lookupObject[lookupProperty]
|
|
end
|
|
|
|
--- Helper function: Reverse a property, split by individual words.
|
|
--- Automatically handles item suffixes ("of the Whale", etc)
|
|
---@param item table Entry from the Bagshui inventory cache.
|
|
---@param propertyValue string String to be reversed.
|
|
---@param propertyName string Item property, used to determine whether to go into name suffix mode.
|
|
---@return string reversedString Reversed string.
|
|
local function SortGroup_GetReversedByWordsPropertyString(item, propertyValue, propertyName)
|
|
-- Turn "A B C of the X" into "C B A X the of".
|
|
if propertyName == "name" and string.len(item.suffixName or "") > 0 then
|
|
return BsUtil.ReverseStringByWords(item.baseName) .. " " .. BsUtil.ReverseStringByWords(item.suffixName)
|
|
end
|
|
return BsUtil.ReverseStringByWords(propertyValue)
|
|
end
|
|
|
|
--- Do the work of sorting the items in a group.
|
|
---@param groupItems table[] Array of Bagshui inventory cache items.
|
|
---@param groupConfig table Group settings from the layout structure.
|
|
---@param defaultSortOrderId string|number? ID of sort order to use if the group doesn't have one configured. Falls back to BS_DEFAULT_SORT_ORDER_ID if needed.
|
|
function SortOrders:SortGroup(groupItems, groupConfig, defaultSortOrderId)
|
|
-- Only declare the sorting function once.
|
|
-- This function consumes self._sortGroup_SortFields as a way to get another parameter into the function.
|
|
if not self._sortGroup_Sort then
|
|
self._sortGroup_Sort = function(itmA, itmB)
|
|
-- Loop through all sort fields in order and check each one to see if it's applicable
|
|
for i, sort in ipairs(self._sortGroup_SortFields) do
|
|
-- Start with some defaults.
|
|
local itemProperty = sort.field
|
|
local lookupTable = nil
|
|
|
|
-- Determine whether this is a lookup field.
|
|
if sort.lookup and self.itemFieldLookups[sort.lookup] then
|
|
itemProperty = self.itemFieldLookups[sort.lookup].property
|
|
lookupTable = self.itemFieldLookups[sort.lookup].table
|
|
end
|
|
|
|
-- Pull the value of the property we're sorting by.
|
|
local valA = itmA[itemProperty]
|
|
local valB = itmB[itemProperty]
|
|
|
|
-- Special cases.
|
|
if lookupTable then
|
|
-- For lookup mode, we need to replace valA and valB with the lookup property values.
|
|
valA = SortGroup_GetLookupFieldProperty(lookupTable[valA], sort.field)
|
|
valB = SortGroup_GetLookupFieldProperty(lookupTable[valB], sort.field)
|
|
elseif sort.reverseWords then
|
|
-- Reverse properties if requested.
|
|
valA = SortGroup_GetReversedByWordsPropertyString(itmA, valA, itemProperty)
|
|
valB = SortGroup_GetReversedByWordsPropertyString(itmB, valB, itemProperty)
|
|
end
|
|
|
|
-- Can't do anything if both are nil.
|
|
if type(valA) == "nil" and type(valB) == "nil" then
|
|
return false
|
|
end
|
|
|
|
-- Make sure sort is not case-sensitive.
|
|
if type(valA) == "string" then
|
|
valA = string.lower(valA)
|
|
end
|
|
if type(valB) == "string" then
|
|
valB = string.lower(valB)
|
|
end
|
|
|
|
-- Make sure we don't try to compare to nil.
|
|
if type(valA) ~= "nil" and type(valB) == "nil" then
|
|
valB = type(valA) == "number" and 0 or ""
|
|
elseif type(valA) == "nil" and type(valB) ~= "nil" then
|
|
valA = type(valB) == "number" and 0 or ""
|
|
end
|
|
|
|
-- Return the proper value depending on whether this is ascending or
|
|
-- descending sort (true if valA should come before valB).
|
|
if (sort.direction == "asc" and valA < valB) or (sort.direction == "desc" and valA > valB) then
|
|
return true
|
|
elseif (sort.direction == "asc" and valA > valB) or (sort.direction == "desc" and valA < valB) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- SortGroup() actually starts here.
|
|
|
|
-- Safety check.
|
|
if type(groupItems) ~= "table" then
|
|
return
|
|
end
|
|
|
|
-- Start by assuming the default sort order.
|
|
local sortOrderId = (defaultSortOrderId and self.list[defaultSortOrderId]) and defaultSortOrderId
|
|
or BS_DEFAULT_SORT_ORDER_ID
|
|
|
|
-- If the group has a valid sort order, change to that.
|
|
if type(groupConfig) == "table" and groupConfig.sortOrder then
|
|
if self.list[groupConfig.sortOrder] then
|
|
sortOrderId = groupConfig.sortOrder
|
|
else
|
|
-- If the configured sort order ID doesn't exist, reset it to to default.
|
|
groupConfig.sortOrder = nil
|
|
end
|
|
end
|
|
|
|
-- Sort the groupItems table.
|
|
self._sortGroup_SortFields = self.list[sortOrderId].fields
|
|
table.sort(groupItems, self._sortGroup_Sort)
|
|
end
|
|
end)
|