mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-04-07 01:15:44 +02:00
Added sorting by sequence for series and collapsing series in series view
This commit is contained in:
parent
c1035d97e8
commit
b1111912f7
@ -28,13 +28,19 @@
|
|||||||
<span class="font-mono">{{ numShowing }}</span>
|
<span class="font-mono">{{ numShowing }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn color="primary" small :loading="processingSeries" class="flex items-center" @click="markSeriesFinished">
|
<ui-checkbox v-model="settings.collapseBookSeries" label="Collapse Series" checkbox-bg="bg"
|
||||||
|
check-color="white" small class="mr-2" @input="updateCollapseBookSeries" />
|
||||||
|
<ui-btn color="primary" small :loading="processingSeries" class="flex items-center ml-1 sm:ml-4"
|
||||||
|
@click="markSeriesFinished">
|
||||||
<div class="h-5 w-5">
|
<div class="h-5 w-5">
|
||||||
<svg v-if="isSeriesFinished" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(63, 181, 68)">
|
<svg v-if="isSeriesFinished" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||||
<path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z" />
|
fill="rgb(63, 181, 68)">
|
||||||
|
<path
|
||||||
|
d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-7 19.6l-7-4.66V3h14v12.93l-7 4.67zm-2.01-7.42l-2.58-2.59L6 12l4 4 8-8-1.42-1.42z" />
|
<path
|
||||||
|
d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-7 19.6l-7-4.66V3h14v12.93l-7 4.67zm-2.01-7.42l-2.58-2.59L6 12l4 4 8-8-1.42-1.42z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span class="pl-2"> Mark Series {{ isSeriesFinished ? 'Not Finished' : 'Finished' }}</span>
|
<span class="pl-2"> Mark Series {{ isSeriesFinished ? 'Not Finished' : 'Finished' }}</span>
|
||||||
@ -280,6 +286,9 @@ export default {
|
|||||||
updateCollapseSeries() {
|
updateCollapseSeries() {
|
||||||
this.saveSettings()
|
this.saveSettings()
|
||||||
},
|
},
|
||||||
|
updateCollapseBookSeries() {
|
||||||
|
this.saveSettings()
|
||||||
|
},
|
||||||
saveSettings() {
|
saveSettings() {
|
||||||
this.$store.dispatch('user/updateUserSettings', this.settings)
|
this.$store.dispatch('user/updateUserSettings', this.settings)
|
||||||
},
|
},
|
||||||
|
@ -122,6 +122,9 @@ export default {
|
|||||||
collapseSeries() {
|
collapseSeries() {
|
||||||
return this.$store.getters['user/getUserSetting']('collapseSeries')
|
return this.$store.getters['user/getUserSetting']('collapseSeries')
|
||||||
},
|
},
|
||||||
|
collapseBookSeries() {
|
||||||
|
return this.$store.getters['user/getUserSetting']('collapseBookSeries')
|
||||||
|
},
|
||||||
coverAspectRatio() {
|
coverAspectRatio() {
|
||||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||||
},
|
},
|
||||||
@ -452,6 +455,9 @@ export default {
|
|||||||
searchParams.set('filter', this.seriesFilterBy)
|
searchParams.set('filter', this.seriesFilterBy)
|
||||||
} else if (this.page === 'series-books') {
|
} else if (this.page === 'series-books') {
|
||||||
searchParams.set('filter', `series.${this.$encode(this.seriesId)}`)
|
searchParams.set('filter', `series.${this.$encode(this.seriesId)}`)
|
||||||
|
if (this.collapseBookSeries) {
|
||||||
|
searchParams.set('collapseseries', 1)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.filterBy && this.filterBy !== 'all') {
|
if (this.filterBy && this.filterBy !== 'all') {
|
||||||
searchParams.set('filter', this.filterBy)
|
searchParams.set('filter', this.filterBy)
|
||||||
@ -467,8 +473,6 @@ export default {
|
|||||||
return searchParams.toString()
|
return searchParams.toString()
|
||||||
},
|
},
|
||||||
checkUpdateSearchParams() {
|
checkUpdateSearchParams() {
|
||||||
if (this.page === 'series-books') return false
|
|
||||||
|
|
||||||
var newSearchParams = this.buildSearchParams()
|
var newSearchParams = this.buildSearchParams()
|
||||||
var currentQueryString = window.location.search
|
var currentQueryString = window.location.search
|
||||||
if (currentQueryString && currentQueryString.startsWith('?')) currentQueryString = currentQueryString.slice(1)
|
if (currentQueryString && currentQueryString.startsWith('?')) currentQueryString = currentQueryString.slice(1)
|
||||||
|
@ -22,12 +22,16 @@
|
|||||||
}}</p>
|
}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="seriesSequenceList"
|
<div v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right"
|
||||||
class="absolute z-20 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center bg-black bg-opacity-90 box-shadow-md"
|
:style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }"
|
||||||
style="background-color: #78350f">#{{ seriesSequenceList }}</div>
|
style="background-color: #78350f">
|
||||||
<div v-else-if="booksInSeries"
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequenceList }}</p>
|
||||||
class="absolute z-20 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center"
|
</div>
|
||||||
style="background-color: #cd9d49dd">{{ booksInSeries }}</div>
|
<div v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20"
|
||||||
|
:style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }"
|
||||||
|
style="background-color: #cd9d49dd">
|
||||||
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ booksInSeries }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
||||||
<div v-show="libraryItem && !imageReady"
|
<div v-show="libraryItem && !imageReady"
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
||||||
<button type="button" class="relative w-full h-full bg-fg border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu">
|
<button type="button"
|
||||||
|
class="relative w-full h-full bg-fg border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer"
|
||||||
|
aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label"
|
||||||
|
@click.prevent="showMenu = !showMenu">
|
||||||
<span class="flex items-center justify-between">
|
<span class="flex items-center justify-between">
|
||||||
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
|
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
|
||||||
<span class="material-icons text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
<span class="material-icons text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-80 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm" role="listbox" aria-labelledby="listbox-label">
|
<ul v-show="showMenu"
|
||||||
|
class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
|
||||||
|
role="listbox" aria-labelledby="listbox-label">
|
||||||
<template v-for="item in selectItems">
|
<template v-for="item in selectItems">
|
||||||
<li :key="item.value" class="text-gray-50 select-none relative py-2 pr-9 cursor-pointer hover:bg-black-400" :class="item.value === selected ? 'bg-primary bg-opacity-50' : ''" role="option" @click="clickedOption(item.value)">
|
<li :key="item.value" class="text-gray-50 select-none relative py-2 pr-9 cursor-pointer hover:bg-black-400"
|
||||||
|
:class="item.value === selected ? 'bg-primary bg-opacity-50' : ''" role="option"
|
||||||
|
@click="clickedOption(item.value)">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="font-normal ml-3 block truncate text-xs">{{ item.text }}</span>
|
<span class="font-normal ml-3 block truncate text-xs">{{ item.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
|
<span v-if="item.value === selected"
|
||||||
|
class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
|
||||||
<span class="material-icons text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
<span class="material-icons text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
@ -29,46 +37,54 @@ export default {
|
|||||||
descending: Boolean
|
descending: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
const bookItems = [
|
||||||
|
{
|
||||||
|
text: 'Title',
|
||||||
|
value: 'media.metadata.title'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Author (First Last)',
|
||||||
|
value: 'media.metadata.authorName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Author (Last, First)',
|
||||||
|
value: 'media.metadata.authorNameLF'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Published Year',
|
||||||
|
value: 'media.metadata.publishedYear'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Added At',
|
||||||
|
value: 'addedAt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Size',
|
||||||
|
value: 'size'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Duration',
|
||||||
|
value: 'media.duration'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'File Birthtime',
|
||||||
|
value: 'birthtimeMs'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'File Modified',
|
||||||
|
value: 'mtimeMs'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const seriesItems = [...bookItems, {
|
||||||
|
text: 'Sequence',
|
||||||
|
value: 'sequence'
|
||||||
|
}]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showMenu: false,
|
showMenu: false,
|
||||||
bookItems: [
|
bookItems: bookItems,
|
||||||
{
|
seriesItems: seriesItems,
|
||||||
text: 'Title',
|
|
||||||
value: 'media.metadata.title'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Author (First Last)',
|
|
||||||
value: 'media.metadata.authorName'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Author (Last, First)',
|
|
||||||
value: 'media.metadata.authorNameLF'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Published Year',
|
|
||||||
value: 'media.metadata.publishedYear'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Added At',
|
|
||||||
value: 'addedAt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Size',
|
|
||||||
value: 'size'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Duration',
|
|
||||||
value: 'media.duration'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'File Birthtime',
|
|
||||||
value: 'birthtimeMs'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'File Modified',
|
|
||||||
value: 'mtimeMs'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
podcastItems: [
|
podcastItems: [
|
||||||
{
|
{
|
||||||
text: 'Title',
|
text: 'Title',
|
||||||
@ -122,8 +138,21 @@ export default {
|
|||||||
return this.$store.getters['libraries/getCurrentLibraryMediaType'] == 'podcast'
|
return this.$store.getters['libraries/getCurrentLibraryMediaType'] == 'podcast'
|
||||||
},
|
},
|
||||||
selectItems() {
|
selectItems() {
|
||||||
if (this.isPodcast) return this.podcastItems
|
let items = null
|
||||||
return this.bookItems
|
if (this.isPodcast) {
|
||||||
|
items = this.podcastItems
|
||||||
|
} else if (this.$store.getters['user/getUserSetting']('filterBy').startsWith('series.')) {
|
||||||
|
items = this.seriesItems
|
||||||
|
} else {
|
||||||
|
items = this.bookItems
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!items.some(i => i.value === this.selected)) {
|
||||||
|
this.selected = items[0].value
|
||||||
|
this.selectedDesc = !this.defaultsToAsc(items[0].value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
},
|
},
|
||||||
selectedText() {
|
selectedText() {
|
||||||
var _selected = this.selected
|
var _selected = this.selected
|
||||||
@ -143,12 +172,19 @@ export default {
|
|||||||
this.selectedDesc = !this.selectedDesc
|
this.selectedDesc = !this.selectedDesc
|
||||||
} else {
|
} else {
|
||||||
this.selected = val
|
this.selected = val
|
||||||
if (val == 'media.metadata.title' || val == 'media.metadata.author' || val == 'media.metadata.authorName' || val == 'media.metadata.authorNameLF') {
|
if (this.defaultsToAsc(val)) this.selectedDesc = false
|
||||||
this.selectedDesc = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.showMenu = false
|
this.showMenu = false
|
||||||
this.$nextTick(() => this.$emit('change', val))
|
this.$nextTick(() => this.$emit('change', val))
|
||||||
|
},
|
||||||
|
defaultsToAsc(val) {
|
||||||
|
return (
|
||||||
|
val == 'media.metadata.title' ||
|
||||||
|
val == 'media.metadata.author' ||
|
||||||
|
val == 'media.metadata.authorName' ||
|
||||||
|
val == 'media.metadata.authorNameLF' ||
|
||||||
|
val == 'sequence'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,7 @@ class LibraryController {
|
|||||||
|
|
||||||
if (!(collapsedItems.length == 1 && collapsedItems[0].collapsedSeries)) {
|
if (!(collapsedItems.length == 1 && collapsedItems[0].collapsedSeries)) {
|
||||||
libraryItems = collapsedItems
|
libraryItems = collapsedItems
|
||||||
payload.total = collapsedItems.length
|
payload.total = libraryItems.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,8 +207,10 @@ class LibraryController {
|
|||||||
sortKey += 'IgnorePrefix'
|
sortKey += 'IgnorePrefix'
|
||||||
}
|
}
|
||||||
|
|
||||||
// If series are collapsed and not sorting by title, sort all collapsed series to the end in alphabetical order
|
// If series are collapsed and not sorting by title or sequence,
|
||||||
if (payload.collapseseries && !sortByTitle) {
|
// sort all collapsed series to the end in alphabetical order
|
||||||
|
const sortBySequence = filterSeries && (sortKey === 'sequence')
|
||||||
|
if (payload.collapseseries && !(sortByTitle || sortBySequence)) {
|
||||||
sortArray.push({
|
sortArray.push({
|
||||||
asc: (li) => {
|
asc: (li) => {
|
||||||
if (li.collapsedSeries) {
|
if (li.collapsedSeries) {
|
||||||
@ -222,10 +224,13 @@ class LibraryController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort series based on the sortBy attribute
|
||||||
var direction = payload.sortDesc ? 'desc' : 'asc'
|
var direction = payload.sortDesc ? 'desc' : 'asc'
|
||||||
sortArray.push({
|
sortArray.push({
|
||||||
[direction]: (li) => {
|
[direction]: (li) => {
|
||||||
if (mediaIsBook && sortByTitle && li.collapsedSeries) {
|
if (mediaIsBook && sortBySequence) {
|
||||||
|
return li.media.metadata.getSeries(filterSeries).sequence
|
||||||
|
} else if (mediaIsBook && sortByTitle && li.collapsedSeries) {
|
||||||
return this.db.serverSettings.sortingIgnorePrefix ?
|
return this.db.serverSettings.sortingIgnorePrefix ?
|
||||||
li.collapsedSeries.nameIgnorePrefix :
|
li.collapsedSeries.nameIgnorePrefix :
|
||||||
li.collapsedSeries.name
|
li.collapsedSeries.name
|
||||||
@ -269,24 +274,24 @@ class LibraryController {
|
|||||||
// If collapsing by series and filtering by a series, generate the list of sequences the collapsed
|
// If collapsing by series and filtering by a series, generate the list of sequences the collapsed
|
||||||
// series represents in the filtered series
|
// series represents in the filtered series
|
||||||
if (filterSeries) {
|
if (filterSeries) {
|
||||||
json.collapsedSeries.seriesSequenceList = li.collapsedSeries.books
|
json.collapsedSeries.seriesSequenceList =
|
||||||
.map(b => parseFloat(b.filterSeriesSequence))
|
naturalSort(li.collapsedSeries.books.map(b => b.filterSeriesSequence)).asc()
|
||||||
.filter(s => s)
|
.reduce((ranges, currentSequence) => {
|
||||||
.sort((a, b) => a - b)
|
let lastRange = ranges.at(-1)
|
||||||
.reduce((ranges, currentSequence) => {
|
let isNumber = /^(\d+|\d+\.\d*|\d*\.\d+)$/.test(currentSequence)
|
||||||
let lastRange = ranges.at(-1)
|
if (isNumber) currentSequence = parseFloat(currentSequence)
|
||||||
|
|
||||||
if (lastRange && ((lastRange.end + 1) == currentSequence)) {
|
if (lastRange && isNumber && lastRange.isNumber && ((lastRange.end + 1) == currentSequence)) {
|
||||||
lastRange.end = currentSequence
|
lastRange.end = currentSequence
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ranges.push({ start: currentSequence, end: currentSequence })
|
ranges.push({ start: currentSequence, end: currentSequence, isNumber: isNaN(currentSequence) })
|
||||||
}
|
}
|
||||||
|
|
||||||
return ranges
|
return ranges
|
||||||
}, [])
|
}, [])
|
||||||
.map(r => r.start == r.end ? r.start : `${r.start}-${r.end}`)
|
.map(r => r.start == r.end ? r.start : `${r.start}-${r.end}`)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
}
|
}
|
||||||
} else if (filterSeries) {
|
} else if (filterSeries) {
|
||||||
// If filtering by series, make sure to include the series metadata
|
// If filtering by series, make sure to include the series metadata
|
||||||
|
Loading…
Reference in New Issue
Block a user