From 7d8b857c771940cdd0294a6f852abc60f0576d0b Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 28 Jul 2025 14:58:28 -0400 Subject: [PATCH] Add book library sort by progress updated #1215 --- client/components/cards/LazyBookCard.vue | 14 +++++++++++++- client/components/controls/LibrarySortSelect.vue | 4 ++++ client/store/user.js | 2 +- client/strings/en-us.json | 2 ++ server/utils/queries/libraryItemsBookFilters.js | 15 ++++++++++++--- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index 41b73310..955e18d9 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -101,7 +101,8 @@

- Episode #{{ recentEpisodeNumber }} + Episode + #{{ recentEpisodeNumber }}

@@ -200,6 +201,9 @@ export default { dateFormat() { return this.store.getters['getServerSetting']('dateFormat') }, + timeFormat() { + return this.store.getters['getServerSetting']('timeFormat') + }, _libraryItem() { return this.libraryItem || {} }, @@ -345,6 +349,10 @@ export default { if (this.mediaMetadata.publishedYear) return this.$getString('LabelPublishedDate', [this.mediaMetadata.publishedYear]) return '\u00A0' } + if (this.orderBy === 'progress') { + if (!this.userProgressLastUpdated) return '\u00A0' + return this.$getString('LabelLastProgressDate', [this.$formatDatetime(this.userProgressLastUpdated, this.dateFormat, this.timeFormat)]) + } return null }, episodeProgress() { @@ -377,6 +385,10 @@ export default { let progressPercent = this.itemIsFinished ? 1 : this.booksInSeries ? this.seriesProgressPercent : this.useEBookProgress ? this.userProgress?.ebookProgress || 0 : this.userProgress?.progress || 0 return Math.max(Math.min(1, progressPercent), 0) }, + userProgressLastUpdated() { + if (!this.userProgress) return null + return this.userProgress.lastUpdate + }, itemIsFinished() { if (this.booksInSeries) return this.seriesIsFinished return this.userProgress ? !!this.userProgress.isFinished : false diff --git a/client/components/controls/LibrarySortSelect.vue b/client/components/controls/LibrarySortSelect.vue index 7e83928f..facd62c0 100644 --- a/client/components/controls/LibrarySortSelect.vue +++ b/client/components/controls/LibrarySortSelect.vue @@ -130,6 +130,10 @@ export default { text: this.$strings.LabelFileModified, value: 'mtimeMs' }, + { + text: this.$strings.LabelLibrarySortByProgress, + value: 'progress' + }, { text: this.$strings.LabelRandomly, value: 'random' diff --git a/client/store/user.js b/client/store/user.js index a67eae34..158ec8f4 100644 --- a/client/store/user.js +++ b/client/store/user.js @@ -92,7 +92,7 @@ export const actions = { if (state.settings.orderBy == 'media.duration') { settingsUpdate.orderBy = 'media.numTracks' } - if (state.settings.orderBy == 'media.metadata.publishedYear') { + if (state.settings.orderBy == 'media.metadata.publishedYear' || state.settings.orderBy == 'progress') { settingsUpdate.orderBy = 'media.metadata.title' } const invalidFilters = ['series', 'authors', 'narrators', 'publishers', 'publishedDecades', 'languages', 'progress', 'issues', 'ebooks', 'abridged'] diff --git a/client/strings/en-us.json b/client/strings/en-us.json index 84cddb66..6dba7adb 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -418,6 +418,7 @@ "LabelLanguages": "Languages", "LabelLastBookAdded": "Last Book Added", "LabelLastBookUpdated": "Last Book Updated", + "LabelLastProgressDate": "Last progress: {0}", "LabelLastSeen": "Last Seen", "LabelLastTime": "Last Time", "LabelLastUpdate": "Last Update", @@ -430,6 +431,7 @@ "LabelLibraryFilterSublistEmpty": "No {0}", "LabelLibraryItem": "Library Item", "LabelLibraryName": "Library Name", + "LabelLibrarySortByProgress": "Progress Updated", "LabelLimit": "Limit", "LabelLineSpacing": "Line spacing", "LabelListenAgain": "Listen Again", diff --git a/server/utils/queries/libraryItemsBookFilters.js b/server/utils/queries/libraryItemsBookFilters.js index ded712cf..85d7f387 100644 --- a/server/utils/queries/libraryItemsBookFilters.js +++ b/server/utils/queries/libraryItemsBookFilters.js @@ -399,9 +399,6 @@ module.exports = { if (filterGroup !== 'series' && sortBy === 'sequence') { sortBy = 'media.metadata.title' } - if (filterGroup !== 'progress' && sortBy === 'progress') { - sortBy = 'media.metadata.title' - } const includeRSSFeed = include.includes('rssfeed') const includeMediaItemShare = !!user?.isAdminOrUp && include.includes('share') @@ -532,6 +529,18 @@ module.exports = { } } + // When sorting by progress but not filtering by progress, include media progresses + if (filterGroup !== 'progress' && sortBy === 'progress') { + bookIncludes.push({ + model: Database.mediaProgressModel, + attributes: ['id', 'isFinished', 'currentTime', 'ebookProgress', 'updatedAt'], + where: { + userId: user.id + }, + required: false + }) + } + let { mediaWhere, replacements } = this.getMediaGroupQuery(filterGroup, filterValue) let bookWhere = Array.isArray(mediaWhere) ? mediaWhere : [mediaWhere]