-- Bagshui Character Data Management UI -- Exposes: BsCharacterData (and Bagshui.components.CharacterData) -- Raises: BAGSHUI_CHARACTERDATA_UPDATE -- Consumes: BAGSHUI_CHARACTERDATA_UPDATE Bagshui:AddComponent(function() -- CharacterData class is built on the ObjectList prototype, even though it -- only exists to provide a UI for deleting old alts. local CharacterData = Bagshui.prototypes.ObjectList:New({ dataStorageKey = "characters", objectName = "CharacterData", objectNamePlural = "CharacterData", wikiPage = BS_WIKI_PAGES.CharacterData, -- Disable all table content filters. objectSkeleton = BS_OBJECT_LIST_TEMPLATE_TABLE_WILDCARD, objectTemplate = BS_OBJECT_LIST_TEMPLATE_TABLE_WILDCARD, managerMultiSelect = false, disableObjectCreation = true, disableObjectEditing = true, disableObjectSharing = true, disableObjectDeletionFunc = function(characterId) -- Loop for scrollable list entries. if type(characterId) == "table" then for charId, _ in pairs(characterId) do if charId == Bagshui.currentCharacterId or Bagshui.sharedCharacters[charId] then return true end end end -- Single object ID. -- Characters from other accounts have to be deleted from the account they belong to. return characterId == Bagshui.currentCharacterId or Bagshui.sharedCharacters[characterId] ~= nil end, -- This metatable will be assigned to each character in SavedVariables BagshuiData.characters. -- It's basically a hack that quickly makes the character data structures compatible -- with ObjectList's expectations, since ObjectList wasn't originally designed -- to handle an object that stores its primary data in a sub-table. (Adding this metatable -- is safe because there is no arbitrary access to keys in the character table.) objectMetatable = { __index = function(tbl, key) if rawget(tbl, key) then return rawget(tbl, key) end if rawget(tbl, "info") then return rawget(tbl, "info")[key] end return rawget(tbl, key) end, }, ---@type string[] -- List of cached characters that will be used for the Character menu. -- Managed by `CharacterData:Update()`. characterIdList = {}, ---@type string[] -- Same as `characterIdList`, but formatted names. characterNameList = {}, }) Bagshui.environment.BsCharacterData = CharacterData Bagshui.components.CharacterData = CharacterData --- Initialize the CharacterData class. function CharacterData:Init() -- Add sorting functions. self.sortedIdLists.realm = {} self.idListSortFunctions.realm = function(idA, idB) return (self._idListSortFunctions_objectList[idA].realm or "") < (self._idListSortFunctions_objectList[idB].realm or "") end self.sortedIdLists.lastLogout = {} self.idListSortFunctions.lastLogout = function(idA, idB) return (self._idListSortFunctions_objectList[idA].lastLogout or 0) < (self._idListSortFunctions_objectList[idB].lastLogout or 0) end -- Calls ObjectList:Init(). self._super.Init(self) -- ObjectList points `self.list` at the saved characters table (`Bagshui.characters`). -- Replace it with a merged view that also contains characters shared by other accounts, -- which are not saved. See `CharacterData:RefreshList()` and `CharacterData:Delete()`. self.list = {} self:RefreshList() -- Generate the Character auto-split menu's initial list. CharacterData:Update() -- Add auto-split menu that lists all characters. Bagshui.prototypes.Menus:AddAutoSplitMenu(BS_AUTO_SPLIT_MENU_TYPE.CHARACTERS, { defaultIdList = self.characterIdList, nameFunc = function(id) return BsCharacter:FormatCharacterName(id, true) or id end, -- Manage... extraItems = { { text = string.format(L.Symbol_Ellipsis, L.Manage), tooltipTitle = L.Manage, tooltipText = string.format(L.Prefix_Manage, L.CharacterData), checked = false, value = { func = function() Bagshui:CloseMenus() self:Open() end, }, }, }, }) end --- Event handling. ---@param event string Event name. ---@param arg1 any Event argument. function CharacterData:OnEvent(event, arg1) -- Check to see if the ObjectList prototype is handling this event if self._super.OnEvent(self, event, arg1) then return end -- Refresh the Character auto-split menu. if event == "BAGSHUI_CHARACTERDATA_UPDATE" then self:Update() return end end --- Subclass override for InitUI() to handle class-specific details. function CharacterData:InitUi() -- Calls ObjectList:InitUi(). self._super.InitUi( self, nil, -- No custom Object Manager needed. nil, -- No custom Object Editor needed. -- Not much to configure since there's no editing. { managerHeight = 400, managerWidth = 450, managerColumns = { { field = "name", title = L.ObjectManager_Column_Name, widthPercent = "45", currentSortOrder = "ASC", lastSortOrder = "ASC", }, { field = "realm", title = L.ObjectManager_Column_Realm, widthPercent = "25", lastSortOrder = "ASC", }, { field = "lastLogout", title = L.ObjectManager_Column_LastInventoryUpdate, widthPercent = "25", lastSortOrder = "ASC", }, }, -- Show correctly formatted data. managerColumnTextFunc = function(fieldName, id, obj, preliminaryDisplayValue) if fieldName == "name" then local formattedName = BsCharacter:FormatCharacterName(id) or preliminaryDisplayValue if Bagshui.sharedCharacters[id] and type(formattedName) == "string" then formattedName = formattedName .. " " .. GRAY_FONT_COLOR_CODE .. "(other account)" .. FONT_COLOR_CODE_CLOSE end return formattedName elseif fieldName == "lastLogout" then return _G.date( "%Y-%b-%d", type(preliminaryDisplayValue) == "number" and preliminaryDisplayValue or _G.time() ) end return preliminaryDisplayValue end, deletePrompt = L.ObjectManager_DeleteForPrompt, deletePromptExtraInfo = L.CharacterDataManager_DeleteInfo, } ) end --- Rebuild the list shown in the manager: this account's saved characters plus --- characters shared by other accounts (which are never saved here). function CharacterData:RefreshList() BsUtil.TableClear(self.list) for id, character in pairs(Bagshui.characters) do self.list[id] = character end for id, character in pairs(Bagshui.sharedCharacters) do if not self.list[id] then self.list[id] = character end end self:UpdateTrackingLists() end --- Delete a character from this account's saved data. (Subclass override.) --- Because `self.list` is a merged view, the base implementation can't be used. --- The character's shared file is also cleared so other accounts stop seeing it. ---@param characterId string ---@return boolean success function CharacterData:Delete(characterId) if characterId == Bagshui.currentCharacterId or Bagshui.sharedCharacters[characterId] or not Bagshui.characters[characterId] then Bagshui:PrintWarning(string.format(L.ObjectList_ActionNotAllowed, L.Deletion, L.CharacterData)) return false end Bagshui.characters[characterId] = nil if BsCatalog and BsCatalog.ForgetSharedCharacter then BsCatalog:ForgetSharedCharacter(characterId) end self:RefreshList() Bagshui:RaiseEvent(self.objectChangeEvent, false, characterId) return true end -- Keep the Character auto-split menu current when old alts are deleted. function CharacterData:Update() BsUtil.TableClear(self.characterIdList) -- Sort the list of characters alphabetically but sort by realm first. for character, _ in pairs(Bagshui.characters) do if character ~= Bagshui.currentCharacterId then table.insert(self.characterIdList, character) end end -- Characters shared by other accounts can be viewed too. for character, _ in pairs(Bagshui.sharedCharacters) do table.insert(self.characterIdList, character) end local function sortKey(characterId) local characterData = Bagshui.characters[characterId] or Bagshui.sharedCharacters[characterId] return tostring(characterData.info.realm or "") .. " " .. tostring(characterData.info.name or "") end table.sort(self.characterIdList, function(a, b) return sortKey(a) < sortKey(b) end) -- Place the current character at the top. table.insert(self.characterIdList, 1, Bagshui.currentCharacterId) -- Refresh formatted name list. BsUtil.TableClear(self.characterNameList) for _, id in ipairs(self.characterIdList) do table.insert(self.characterNameList, BsCharacter:FormatCharacterName(id, true) or id) end end -- Class event registration. -- This is done at the end because RegisterEvent expects the class to have an OnEvent function. Bagshui:RegisterEvent("BAGSHUI_CHARACTERDATA_UPDATE", CharacterData) end)