function Librarian:InitializeKeybindStripDescriptors() self.keybindStripDescriptor = { { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = GetString(SI_LORE_LIBRARY_READ), keybind = "UI_SHORTCUT_PRIMARY", visible = function() return self.mouseOverRow end, callback = function() self:ReadBook(self.mouseOverRow.data) end, }, { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = function() if not self.mouseOverRow then return nil end local book = self:FindBook(self.mouseOverRow.data.bookId) if book.unread then return GetString(LIBRARIAN_MARK_READ) else return GetString(LIBRARIAN_MARK_UNREAD) end end, keybind = "UI_SHORTCUT_SECONDARY", visible = function() return self.mouseOverRow end, callback = function() local book = self:FindBook(self.mouseOverRow.data.bookId) self:ToggleReadBook(book) end, }, { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = GetString(LIBRARIAN_GO_TO_CATEGORY), keybind = "UI_SHORTCUT_TERTIARY", visible = function() return self.mouseOverRow and self:CanOpenCollection(self.mouseOverRow.data.categoryIndex, self.mouseOverRow.data.collectionIndex) end, callback = function() self:OpenCollection(self.mouseOverRow.data.categoryIndex, self.mouseOverRow.data.collectionIndex, self.mouseOverRow.data.bookIndex) end, }, { alignment = KEYBIND_STRIP_ALIGN_LEFT, name = GetString(LIBRARIAN_DELETE_BOOK), keybind = "UI_SHORTCUT_NEGATIVE", visible = function() return self.mouseOverRow and (not self.mouseOverRow.data.categoryIndex or not self.mouseOverRow.data.collectionIndex or self.mouseOverRow.data.categoryIndex == self.constants.CUSTOM_CATEGORY) end, callback = function() self.potentialBookIdToDelete = self.mouseOverRow.data.bookId ZO_Dialogs_ShowPlatformDialog("LIBRARIAN_DELETE_BOOK", {bookId = self.mouseOverRow.data.bookId}, {mainTextParams = {self.mouseOverRow.data.title}}) end, } } end function Librarian:InitializeScene() if not LIBRARIAN_SCENE then LIBRARIAN_TITLE_FRAGMENT = ZO_SetTitleFragment:New(LIBRARIAN_WINDOW_TITLE_LIBRARIAN) LIBRARIAN_SCENE = ZO_Scene:New("librarian", SCENE_MANAGER) LIBRARIAN_SCENE:AddFragmentGroup(FRAGMENT_GROUP.MOUSE_DRIVEN_UI_WINDOW) if self.settings.enableCharacterSpin then LIBRARIAN_SCENE:AddFragmentGroup(FRAGMENT_GROUP.FRAME_TARGET_STANDARD_RIGHT_PANEL) end LIBRARIAN_SCENE:AddFragment(ZO_FadeSceneFragment:New(LibrarianFrame)) LIBRARIAN_SCENE:AddFragment(RIGHT_BG_FRAGMENT) LIBRARIAN_SCENE:AddFragment(TITLE_FRAGMENT) LIBRARIAN_SCENE:AddFragment(LIBRARIAN_TITLE_FRAGMENT) LIBRARIAN_SCENE:AddFragment(CODEX_WINDOW_SOUNDS) LIBRARIAN_SCENE:RegisterCallback("StateChange", function(oldState, newState) if(newState == SCENE_SHOWING) then KEYBIND_STRIP:AddKeybindButtonGroup(self.keybindStripDescriptor) elseif(newState == SCENE_HIDDEN) then KEYBIND_STRIP:RemoveKeybindButtonGroup(self.keybindStripDescriptor) end end) end end function Librarian:BuildMasterList() local function ShouldDisplayBook(book) if not self.settings.showHiddenBook then local category, collection = self:GetLoreBookIndicesFromBookId(book.bookId) if category and collection then if self:IsLoreCollectionHidden(category, collection) then return false end else return false end end if book.bookId > self.constants.CUSTOM_BOOK_ID_START then local category, collection = self:GetLoreBookIndicesFromBookId(book.bookId) if not category or not collection then return false end end return true end for i, book in ipairs(self.books) do if ShouldDisplayBook(book) then if not self.masterList[i] then self.masterList[i] = {} end local data = self.masterList[i] for k,v in pairs(book) do if k == "body" then data[k] = table.concat(book.body) else data[k] = v end end -- because we reuse the same list between 2 refresh, the data may already be filled up so no need to recompute it, these won't change if not data.categoryIndex or not data.collectionIndex or not data.bookIndex then data.categoryIndex, data.collectionIndex, data.bookIndex = self:GetLoreBookIndicesFromBookId(book.bookId) end if data.categoryIndex and data.collectionIndex and data.bookIndex and (not data.title or not data.body or not data.category) then data.title, data.body = self:GetBookTitleAndBody(data.categoryIndex, data.collectionIndex, data.bookIndex) data.category = self:GetLoreCollectionName(data.categoryIndex, data.collectionIndex) end if not data.category or data.category == "" then data.category = GetString(LIBRARIAN_NO_CATEGORY) end if not data.wordCount or data.wordCount == 0 then local wordCount = 0 if data.body then for w in data.body:gmatch("%S+") do wordCount = wordCount + 1 end end data.wordCount = wordCount end data.type = self.constants.LIBRARIAN_SEARCH local characterBook = self:FindCharacterBook(book.bookId) if characterBook then data.seenByCurrentCharacter = true data.timeStamp = characterBook.timeStamp else data.seenByCurrentCharacter = false data.timeStamp = book.timeStamp end else self.masterList[i] = nil end end end function Librarian:FilterScrollList() local scrollData = ZO_ScrollList_GetDataList(self.list) ZO_ClearNumericallyIndexedTable(scrollData) local bookCount = 0 local unreadCount = 0 local searchTerm = self.searchBox:GetText() for _, data in pairs(self.masterList) do if self.settings.showAllBooks or data.seenByCurrentCharacter then if(searchTerm == "" or self.search:IsMatch(searchTerm, data)) then table.insert(scrollData, ZO_ScrollList_CreateDataEntry(self.constants.LIBRARIAN_DATA, data)) bookCount = bookCount + 1 if data.unread then unreadCount = unreadCount + 1 end end end end local message = string.format(GetString(LIBRARIAN_BOOK_COUNT), bookCount) if unreadCount > 0 then message = string.format(GetString(LIBRARIAN_UNREAD_COUNT), message, unreadCount) end LibrarianFrameBookCount:SetText(message) end function Librarian:SortScrollList() local scrollData = ZO_ScrollList_GetDataList(self.list) table.sort(scrollData, self.sortFunction) end function Librarian:SetupBookRow(control, data) control.data = data control.unread = GetControl(control, "Unread") control.found = GetControl(control, "Found") control.title = GetControl(control, "Title") control.category = GetControl(control, "Category") control.wordCount = GetControl(control, "WordCount") control.unread.nonRecolorable = true if data.unread then control.unread:SetAlpha(1) else control.unread:SetAlpha(0) end control.found.normalColor = ZO_NORMAL_TEXT control.found:SetText(self:FormatClockTime(data.timeStamp)) control.title.normalColor = ZO_NORMAL_TEXT control.title:SetText(data.title) control.category.normalColor = ZO_NORMAL_TEXT control.category:SetText(data.category) control.wordCount.normalColor = ZO_NORMAL_TEXT control.wordCount:SetText(data.wordCount) ZO_SortFilterList.SetupRow(self, control, data) end function Librarian:ProcessBookEntry(stringSearch, data, searchTerm, cache) local lowerSearchTerm = searchTerm:lower() if data.title and zo_plainstrfind(data.title:lower(), lowerSearchTerm) then return true end if not self.searchOnlyTitle and data.body and zo_plainstrfind(data.body:lower(), lowerSearchTerm) then return true end return false end function Librarian:Toggle() if LibrarianFrame:IsControlHidden() then SCENE_MANAGER:Show("librarian") else SCENE_MANAGER:Hide("librarian") end end function Librarian:FormatClockTime(time) local midnightSeconds = GetSecondsSinceMidnight() local utcSeconds = GetTimeStamp() % 86400 local offset = midnightSeconds - utcSeconds if offset < -43200 then offset = offset + 86400 end local dateString = GetDateStringFromTimestamp(time) local timeString = ZO_FormatTime((time + offset) % 86400, TIME_FORMAT_STYLE_CLOCK_TIME, self.settings.timeFormat) return string.format("%s %s", dateString, timeString) end function Librarian:OnSearchTextChanged() ZO_EditDefaultText_OnTextChanged(self.searchBox) -- Let's wait a little bit before updating the filters if the player didn't have time to enter its full string yet -- Because if we try to update the filters too much it sometimes crash local TIME_BEFORE_UPDATING_FILTERS = 600 -- ms self.lastTimeSearchModified = GetGameTimeMilliseconds() local function TryRefreshFilters() if self.lastTimeSearchModified + TIME_BEFORE_UPDATING_FILTERS <= GetGameTimeMilliseconds() then self:RefreshFilters() end end zo_callLater(TryRefreshFilters, TIME_BEFORE_UPDATING_FILTERS) end function Librarian.OnMouseEnterRow(control) LIBRARIAN:Row_OnMouseEnter(control) end function Librarian.OnMouseExitRow(control) LIBRARIAN:Row_OnMouseExit(control) end function Librarian.OnMouseUpRow(control, button, upInside) LIBRARIAN:ReadBook(control.data) end