From ce133cd6f2dbd0adbc3c778a50a51c75928e8d65 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 29 Oct 2022 11:17:51 -0500 Subject: [PATCH] Add:Series sort #712 --- client/components/app/BookShelfToolbar.vue | 25 +++++++++--- client/components/app/LazyBookshelf.vue | 2 +- client/components/cards/LazySeriesCard.vue | 19 +++++++++- client/mixins/bookshelfCardsHelpers.js | 2 + .../pages/library/_library/bookshelf/_id.vue | 7 +++- client/store/libraries.js | 10 +++-- server/controllers/LibraryController.js | 24 +++++++++--- server/utils/libraryHelpers.js | 38 +++++++++++-------- 8 files changed, 93 insertions(+), 34 deletions(-) diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index ed575723..ad1fe226 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -45,7 +45,7 @@ - + Remove All {{ numShowing }} {{ entityName }} @@ -85,19 +85,17 @@ export default { processingSeries: false, processingIssues: false, processingAuthors: false, - seriesSort: 'name', - seriesSortDesc: false, seriesSortItems: [ { text: 'Name', value: 'name' }, { - text: '# of Books', + text: 'Number of Books', value: 'numBooks' }, { - text: 'Added At', + text: 'Date Added', value: 'addedAt' }, { @@ -169,6 +167,22 @@ export default { }, isIssuesFilter() { return this.filterBy === 'issues' && this.$route.query.filter === 'issues' + }, + seriesSortBy: { + get() { + return this.$store.state.libraries.seriesSortBy + }, + set(val) { + this.$store.commit('libraries/setSeriesSortBy', val) + } + }, + seriesSortDesc: { + get() { + return this.$store.state.libraries.seriesSortDesc + }, + set(val) { + this.$store.commit('libraries/setSeriesSortDesc', val) + } } }, methods: { @@ -249,7 +263,6 @@ export default { this.saveSettings() }, updateSeriesSort() { - this.$store.commit('libraries/setSeriesSort', { sort: this.seriesSort, desc: this.seriesSortDesc }) this.$eventBus.$emit('series-sort-updated') }, updateCollapseSeries() { diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index 262b5fba..b863f5e1 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -99,7 +99,7 @@ export default { return this.page }, seriesSortBy() { - return this.$store.state.libraries.seriesSort + return this.$store.state.libraries.seriesSortBy }, seriesSortDesc() { return this.$store.state.libraries.seriesSortDesc diff --git a/client/components/cards/LazySeriesCard.vue b/client/components/cards/LazySeriesCard.vue index 3932646b..4b12325a 100644 --- a/client/components/cards/LazySeriesCard.vue +++ b/client/components/cards/LazySeriesCard.vue @@ -20,6 +20,7 @@

{{ displayTitle }}

+

{{ displaySortLine }}

@@ -40,7 +41,8 @@ export default { type: Object, default: () => null }, - sortingIgnorePrefix: Boolean + sortingIgnorePrefix: Boolean, + orderBy: String }, data() { return { @@ -52,6 +54,9 @@ export default { } }, computed: { + dateFormat() { + return this.store.state.serverSettings.dateFormat + }, labelFontSize() { if (this.width < 160) return 0.75 return 0.875 @@ -73,12 +78,24 @@ export default { if (this.sortingIgnorePrefix) return this.nameIgnorePrefix || this.title return this.title }, + displaySortLine() { + if (this.orderBy === 'addedAt') { + // return this.addedAt + return 'Added ' + this.$formatDate(this.addedAt, this.dateFormat) + } else if (this.orderBy === 'totalDuration') { + return 'Duration: ' + this.$elapsedPrettyExtended(this.totalDuration, false) + } + return null + }, books() { return this.series ? this.series.books || [] : [] }, addedAt() { return this.series ? this.series.addedAt : 0 }, + totalDuration() { + return this.series ? this.series.totalDuration : 0 + }, seriesBookProgress() { return this.books .map((libraryItem) => { diff --git a/client/mixins/bookshelfCardsHelpers.js b/client/mixins/bookshelfCardsHelpers.js index 574dee59..4eec2eea 100644 --- a/client/mixins/bookshelfCardsHelpers.js +++ b/client/mixins/bookshelfCardsHelpers.js @@ -59,6 +59,8 @@ export default { if (this.entityName === 'books') { props.filterBy = this.filterBy props.orderBy = this.orderBy + } else if (this.entityName === 'series') { + props.orderBy = this.seriesSortBy } var _this = this diff --git a/client/pages/library/_library/bookshelf/_id.vue b/client/pages/library/_library/bookshelf/_id.vue index 134246e8..5ed86eae 100644 --- a/client/pages/library/_library/bookshelf/_id.vue +++ b/client/pages/library/_library/bookshelf/_id.vue @@ -14,8 +14,13 @@ export default { return redirect('/oops?message=Library not found') } + // Set series sort by + if (query.sort && params.id === 'series') { + store.commit('libraries/setSeriesSortBy', query.sort) + store.commit('libraries/setSeriesSortDesc', !!query.desc) + } // Set filter by - if (query.filter) { + if (query.filter && params.id !== 'series') { store.dispatch('user/updateUserSettings', { filterBy: query.filter }) } diff --git a/client/store/libraries.js b/client/store/libraries.js index 519ca4a0..fcc986b6 100644 --- a/client/store/libraries.js +++ b/client/store/libraries.js @@ -9,7 +9,7 @@ export const state = () => ({ issues: 0, folderLastUpdate: 0, filterData: null, - seriesSort: 'name', + seriesSortBy: 'name', seriesSortDesc: false }) @@ -292,8 +292,10 @@ export const mutations = { } } }, - setSeriesSort(state, { sort, desc }) { - state.seriesSort = sort - state.seriesSortDesc = desc + setSeriesSortBy(state, sortBy) { + state.seriesSortBy = sortBy + }, + setSeriesSortDesc(state, sortDesc) { + state.seriesSortDesc = sortDesc } } \ No newline at end of file diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index bb75bf24..199a532b 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -222,7 +222,7 @@ class LibraryController { } if (payload.collapseseries) { - libraryItems = libraryHelpers.collapseBookSeries(libraryItems) + libraryItems = libraryHelpers.collapseBookSeries(libraryItems, this.db.series) payload.total = libraryItems.length } else if (filterSeries) { // Book media when filtering series will include series object on media metadata @@ -275,10 +275,24 @@ class LibraryController { minified: req.query.minified === '1' } - var series = libraryHelpers.getSeriesFromBooks(libraryItems, payload.minified) - series = sort(series).asc(s => { - return this.db.serverSettings.sortingIgnorePrefix ? s.nameIgnorePrefix : s.name - }) + var series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, payload.minified) + const direction = payload.sortDesc ? 'desc' : 'asc' + series = naturalSort(series).by([ + { + [direction]: (se) => { + if (payload.sortBy === 'numBooks') { + return se.books.length + } else if (payload.sortBy === 'totalDuration') { + return se.totalDuration + } else if (payload.sortBy === 'addedAt') { + return se.addedAt + } else { // sort by name + return this.db.serverSettings.sortingIgnorePrefix ? se.nameIgnorePrefix : se.name + } + } + } + ]) + payload.total = series.length if (payload.limit) { diff --git a/server/utils/libraryHelpers.js b/server/utils/libraryHelpers.js index f4017ae4..70b22537 100644 --- a/server/utils/libraryHelpers.js +++ b/server/utils/libraryHelpers.js @@ -1,5 +1,5 @@ const { sort, createNewSortInstance } = require('../libs/fastSort') -const { getTitleIgnorePrefix } = require('../utils/index') +const { getTitleIgnorePrefix, isNullOrNaN } = require('../utils/index') const naturalSort = createNewSortInstance({ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare }) @@ -114,23 +114,29 @@ module.exports = { return data }, - getSeriesFromBooks(books, minified = false) { - var _series = {} + getSeriesFromBooks(books, allSeries, minified = false) { + const _series = {} books.forEach((libraryItem) => { - var bookSeries = libraryItem.media.metadata.series || [] - bookSeries.forEach((series) => { - var abJson = minified ? libraryItem.toJSONMinified() : libraryItem.toJSONExpanded() - abJson.sequence = series.sequence - if (!_series[series.id]) { - _series[series.id] = { - id: series.id, - name: series.name, - nameIgnorePrefix: getTitleIgnorePrefix(series.name), + const bookSeries = libraryItem.media.metadata.series || [] + bookSeries.forEach((bookSeriesObj) => { + const series = allSeries.find(se => se.id === bookSeriesObj.id) + + const abJson = minified ? libraryItem.toJSONMinified() : libraryItem.toJSONExpanded() + abJson.sequence = bookSeriesObj.sequence + if (!_series[bookSeriesObj.id]) { + _series[bookSeriesObj.id] = { + id: bookSeriesObj.id, + name: bookSeriesObj.name, + nameIgnorePrefix: getTitleIgnorePrefix(bookSeriesObj.name), type: 'series', - books: [abJson] + books: [abJson], + addedAt: series ? series.addedAt : 0, + totalDuration: isNullOrNaN(abJson.media.duration) ? 0 : Number(abJson.media.duration) } + } else { - _series[series.id].books.push(abJson) + _series[bookSeriesObj.id].books.push(abJson) + _series[bookSeriesObj.id].totalDuration += isNullOrNaN(abJson.media.duration) ? 0 : Number(abJson.media.duration) } }) }) @@ -209,8 +215,8 @@ module.exports = { return totalSize }, - collapseBookSeries(libraryItems) { - var seriesObjects = this.getSeriesFromBooks(libraryItems, true) + collapseBookSeries(libraryItems, series) { + var seriesObjects = this.getSeriesFromBooks(libraryItems, series, true) var seriesToUse = {} var libraryItemIdsToHide = [] seriesObjects.forEach((series) => {