mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-14 09:58:20 +01:00
Add:Series filters #712
This commit is contained in:
parent
ce133cd6f2
commit
fbbcbb4af1
@ -43,8 +43,9 @@
|
|||||||
<div class="flex-grow hidden sm:inline-block" />
|
<div class="flex-grow hidden sm:inline-block" />
|
||||||
|
|
||||||
<ui-checkbox v-if="isLibraryPage && !isPodcastLibrary" v-model="settings.collapseSeries" label="Collapse Series" checkbox-bg="bg" check-color="white" small class="mr-2" @input="updateCollapseSeries" />
|
<ui-checkbox v-if="isLibraryPage && !isPodcastLibrary" v-model="settings.collapseSeries" label="Collapse Series" checkbox-bg="bg" check-color="white" small class="mr-2" @input="updateCollapseSeries" />
|
||||||
<controls-filter-select v-if="isLibraryPage" v-model="settings.filterBy" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateFilter" />
|
<controls-library-filter-select v-if="isLibraryPage" v-model="settings.filterBy" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateFilter" />
|
||||||
<controls-order-select v-if="isLibraryPage" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateOrder" />
|
<controls-library-sort-select v-if="isLibraryPage" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateOrder" />
|
||||||
|
<controls-library-filter-select v-if="isSeriesPage" v-model="seriesFilterBy" is-series class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateSeriesFilter" />
|
||||||
<controls-sort-select v-if="isSeriesPage" v-model="seriesSortBy" :descending.sync="seriesSortDesc" :items="seriesSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateSeriesSort" />
|
<controls-sort-select v-if="isSeriesPage" v-model="seriesSortBy" :descending.sync="seriesSortDesc" :items="seriesSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateSeriesSort" />
|
||||||
|
|
||||||
<ui-btn v-if="isIssuesFilter && userCanDelete" :loading="processingIssues" color="error" small class="ml-4" @click="removeAllIssues">Remove All {{ numShowing }} {{ entityName }}</ui-btn>
|
<ui-btn v-if="isIssuesFilter && userCanDelete" :loading="processingIssues" color="error" small class="ml-4" @click="removeAllIssues">Remove All {{ numShowing }} {{ entityName }}</ui-btn>
|
||||||
@ -183,6 +184,14 @@ export default {
|
|||||||
set(val) {
|
set(val) {
|
||||||
this.$store.commit('libraries/setSeriesSortDesc', val)
|
this.$store.commit('libraries/setSeriesSortDesc', val)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
seriesFilterBy: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.libraries.seriesFilterBy
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$store.commit('libraries/setSeriesFilterBy', val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -265,6 +274,9 @@ export default {
|
|||||||
updateSeriesSort() {
|
updateSeriesSort() {
|
||||||
this.$eventBus.$emit('series-sort-updated')
|
this.$eventBus.$emit('series-sort-updated')
|
||||||
},
|
},
|
||||||
|
updateSeriesFilter() {
|
||||||
|
this.$eventBus.$emit('series-sort-updated')
|
||||||
|
},
|
||||||
updateCollapseSeries() {
|
updateCollapseSeries() {
|
||||||
this.saveSettings()
|
this.saveSettings()
|
||||||
},
|
},
|
||||||
|
@ -104,6 +104,9 @@ export default {
|
|||||||
seriesSortDesc() {
|
seriesSortDesc() {
|
||||||
return this.$store.state.libraries.seriesSortDesc
|
return this.$store.state.libraries.seriesSortDesc
|
||||||
},
|
},
|
||||||
|
seriesFilterBy() {
|
||||||
|
return this.$store.state.libraries.seriesFilterBy
|
||||||
|
},
|
||||||
orderBy() {
|
orderBy() {
|
||||||
return this.$store.getters['user/getUserSetting']('orderBy')
|
return this.$store.getters['user/getUserSetting']('orderBy')
|
||||||
},
|
},
|
||||||
@ -431,6 +434,7 @@ export default {
|
|||||||
if (this.page === 'series') {
|
if (this.page === 'series') {
|
||||||
searchParams.set('sort', this.seriesSortBy)
|
searchParams.set('sort', this.seriesSortBy)
|
||||||
searchParams.set('desc', this.seriesSortDesc ? 1 : 0)
|
searchParams.set('desc', this.seriesSortDesc ? 1 : 0)
|
||||||
|
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)}`)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
|
||||||
<button type="button" class="relative w-full h-full 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="block truncate text-xs">{{ selectedText }}</span>
|
|
||||||
</span>
|
|
||||||
<span v-if="selected === 'all'" class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
|
||||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd" d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div v-else class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-300" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected">
|
|
||||||
<span class="material-icons" style="font-size: 1.1rem">close</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div 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">
|
|
||||||
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
|
||||||
<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)">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="font-normal ml-3 block truncate text-sm md:text-base">{{ item.text }}</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
value: String
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showMenu: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
selected: {
|
|
||||||
get() {
|
|
||||||
return this.value
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
this.$emit('input', val)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectItems() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
value: 'all',
|
|
||||||
text: 'Show All'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'incomplete',
|
|
||||||
text: 'Incomplete'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'complete',
|
|
||||||
text: 'Complete'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'in_progress',
|
|
||||||
text: 'In Progress'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
selectedText() {
|
|
||||||
if (!this.selected) return ''
|
|
||||||
const filter = this.selectItems.find((i) => i.value === this.selected)
|
|
||||||
return filter ? filter.text : ''
|
|
||||||
},
|
|
||||||
filterData() {
|
|
||||||
return this.$store.state.libraries.filterData || {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clearSelected() {
|
|
||||||
this.selected = 'all'
|
|
||||||
this.showMenu = false
|
|
||||||
this.$nextTick(() => this.$emit('change', 'all'))
|
|
||||||
},
|
|
||||||
clickOutside() {
|
|
||||||
this.showMenu = false
|
|
||||||
},
|
|
||||||
clickedOption(option) {
|
|
||||||
var val = option.value
|
|
||||||
if (this.selected === val) {
|
|
||||||
this.showMenu = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.selected = val
|
|
||||||
this.showMenu = false
|
|
||||||
this.$nextTick(() => this.$emit('change', val))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,8 +1,8 @@
|
|||||||
<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 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">{{ selectedText }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="selected === 'all'" class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
<span v-if="selected === 'all'" class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
@ -15,42 +15,12 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div 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">
|
<div 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">
|
||||||
<ul v-show="!sublist" class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
||||||
<template v-for="item in selectItems">
|
<template v-for="item in items">
|
||||||
<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)">
|
<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)">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="font-normal ml-3 block truncate text-sm md:text-base">{{ item.text }}</span>
|
<span class="font-normal ml-3 block truncate text-sm md:text-base">{{ item.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.sublist" class="absolute right-1 top-0 bottom-0 h-full flex items-center">
|
|
||||||
<span class="material-icons">arrow_right</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
<ul v-show="sublist" class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
|
||||||
<li class="text-gray-50 select-none relative py-2 pl-9 cursor-pointer hover:bg-black-400" role="option" @click="sublist = null">
|
|
||||||
<div class="absolute left-1 top-0 bottom-0 h-full flex items-center">
|
|
||||||
<span class="material-icons">arrow_left</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="font-normal ml-3 block truncate">Back</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li v-if="!sublistItems.length" class="text-gray-400 select-none relative px-2" role="option">
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
<span class="font-normal block truncate py-2">No {{ sublist }}</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li v-else-if="sublist === 'series'" class="text-gray-50 select-none relative px-2 cursor-pointer hover:bg-black-400" role="option" @click="clickedSublistOption($encode('No Series'))">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="font-normal block truncate py-2 text-xs text-white text-opacity-80">No Series</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<template v-for="item in sublistItems">
|
|
||||||
<li :key="item.value" class="text-gray-50 select-none relative px-2 cursor-pointer hover:bg-black-400" :class="`${sublist}.${item.value}` === selected ? 'bg-primary bg-opacity-50' : ''" role="option" @click="clickedSublistOption(item.value)">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="font-normal truncate py-2 text-xs">{{ item.text }}</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
@ -61,97 +31,15 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
value: String
|
value: String,
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showMenu: false,
|
showMenu: false
|
||||||
sublist: null,
|
|
||||||
bookItems: [
|
|
||||||
{
|
|
||||||
text: 'All',
|
|
||||||
value: 'all'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Genre',
|
|
||||||
value: 'genres',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Tag',
|
|
||||||
value: 'tags',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Series',
|
|
||||||
value: 'series',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Authors',
|
|
||||||
value: 'authors',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Narrator',
|
|
||||||
value: 'narrators',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Language',
|
|
||||||
value: 'languages',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Progress',
|
|
||||||
value: 'progress',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Missing',
|
|
||||||
value: 'missing',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Issues',
|
|
||||||
value: 'issues',
|
|
||||||
sublist: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'RSS Feed Open',
|
|
||||||
value: 'feed-open',
|
|
||||||
sublist: false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
podcastItems: [
|
|
||||||
{
|
|
||||||
text: 'All',
|
|
||||||
value: 'all'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Genre',
|
|
||||||
value: 'genres',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Tag',
|
|
||||||
value: 'tags',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Issues',
|
|
||||||
value: 'issues',
|
|
||||||
sublist: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
showMenu(newVal) {
|
|
||||||
if (!newVal) {
|
|
||||||
if (this.sublist && !this.selectedItemSublist) this.sublist = null
|
|
||||||
if (!this.sublist && this.selectedItemSublist) this.sublist = this.selectedItemSublist
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -163,81 +51,10 @@ export default {
|
|||||||
this.$emit('input', val)
|
this.$emit('input', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isPodcast() {
|
|
||||||
return this.$store.getters['libraries/getCurrentLibraryMediaType'] == 'podcast'
|
|
||||||
},
|
|
||||||
selectItems() {
|
|
||||||
if (this.isPodcast) return this.podcastItems
|
|
||||||
return this.bookItems
|
|
||||||
},
|
|
||||||
selectedItemSublist() {
|
|
||||||
return this.selected && this.selected.includes('.') ? this.selected.split('.')[0] : false
|
|
||||||
},
|
|
||||||
selectedText() {
|
selectedText() {
|
||||||
if (!this.selected) return ''
|
if (!this.selected) return ''
|
||||||
var parts = this.selected.split('.')
|
const filter = this.items.find((i) => i.value === this.selected)
|
||||||
var filterName = this.selectItems.find((i) => i.value === parts[0])
|
return filter ? filter.text : ''
|
||||||
var filterValue = null
|
|
||||||
if (parts.length > 1) {
|
|
||||||
var decoded = this.$decode(parts[1])
|
|
||||||
if (decoded.startsWith('aut_')) {
|
|
||||||
var author = this.authors.find((au) => au.id == decoded)
|
|
||||||
if (author) filterValue = author.name
|
|
||||||
} else if (decoded.startsWith('ser_')) {
|
|
||||||
var series = this.series.find((se) => se.id == decoded)
|
|
||||||
if (series) filterValue = series.name
|
|
||||||
} else {
|
|
||||||
filterValue = decoded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (filterName && filterValue) {
|
|
||||||
return `${filterName.text}: ${filterValue}`
|
|
||||||
} else if (filterName) {
|
|
||||||
return filterName.text
|
|
||||||
} else if (filterValue) {
|
|
||||||
return filterValue
|
|
||||||
} else {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
genres() {
|
|
||||||
return this.filterData.genres || []
|
|
||||||
},
|
|
||||||
tags() {
|
|
||||||
return this.filterData.tags || []
|
|
||||||
},
|
|
||||||
series() {
|
|
||||||
return this.filterData.series || []
|
|
||||||
},
|
|
||||||
authors() {
|
|
||||||
return this.filterData.authors || []
|
|
||||||
},
|
|
||||||
narrators() {
|
|
||||||
return this.filterData.narrators || []
|
|
||||||
},
|
|
||||||
languages() {
|
|
||||||
return this.filterData.languages || []
|
|
||||||
},
|
|
||||||
progress() {
|
|
||||||
return ['Finished', 'In Progress', 'Not Started', 'Not Finished']
|
|
||||||
},
|
|
||||||
missing() {
|
|
||||||
return ['ASIN', 'ISBN', 'Subtitle', 'Author', 'Publish Year', 'Series', 'Description', 'Genres', 'Tags', 'Narrator', 'Publisher', 'Language']
|
|
||||||
},
|
|
||||||
sublistItems() {
|
|
||||||
return (this[this.sublist] || []).map((item) => {
|
|
||||||
if (typeof item === 'string') {
|
|
||||||
return {
|
|
||||||
text: item,
|
|
||||||
value: this.$encode(item)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
text: item.name,
|
|
||||||
value: this.$encode(item.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
filterData() {
|
filterData() {
|
||||||
return this.$store.state.libraries.filterData || {}
|
return this.$store.state.libraries.filterData || {}
|
||||||
@ -250,18 +67,9 @@ export default {
|
|||||||
this.$nextTick(() => this.$emit('change', 'all'))
|
this.$nextTick(() => this.$emit('change', 'all'))
|
||||||
},
|
},
|
||||||
clickOutside() {
|
clickOutside() {
|
||||||
if (!this.selectedItemSublist) this.sublist = null
|
|
||||||
this.showMenu = false
|
this.showMenu = false
|
||||||
},
|
},
|
||||||
clickedSublistOption(item) {
|
|
||||||
this.clickedOption({ value: `${this.sublist}.${item}` })
|
|
||||||
},
|
|
||||||
clickedOption(option) {
|
clickedOption(option) {
|
||||||
if (option.sublist) {
|
|
||||||
this.sublist = option.value
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var val = option.value
|
var val = option.value
|
||||||
if (this.selected === val) {
|
if (this.selected === val) {
|
||||||
this.showMenu = false
|
this.showMenu = false
|
||||||
|
314
client/components/controls/LibraryFilterSelect.vue
Normal file
314
client/components/controls/LibraryFilterSelect.vue
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
<template>
|
||||||
|
<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">
|
||||||
|
<span class="flex items-center justify-between">
|
||||||
|
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
|
||||||
|
</span>
|
||||||
|
<span v-if="selected === 'all'" class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||||
|
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd" d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<div v-else class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-300" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected">
|
||||||
|
<span class="material-icons" style="font-size: 1.1rem">close</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div 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">
|
||||||
|
<ul v-show="!sublist" class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
||||||
|
<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)">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="font-normal ml-3 block truncate text-sm md:text-base">{{ item.text }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.sublist" class="absolute right-1 top-0 bottom-0 h-full flex items-center">
|
||||||
|
<span class="material-icons">arrow_right</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
<ul v-show="sublist" class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
||||||
|
<li class="text-gray-50 select-none relative py-2 pl-9 cursor-pointer hover:bg-black-400" role="option" @click="sublist = null">
|
||||||
|
<div class="absolute left-1 top-0 bottom-0 h-full flex items-center">
|
||||||
|
<span class="material-icons">arrow_left</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="font-normal ml-3 block truncate">Back</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li v-if="!sublistItems.length" class="text-gray-400 select-none relative px-2" role="option">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span class="font-normal block truncate py-2">No {{ sublist }}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li v-else-if="sublist === 'series'" class="text-gray-50 select-none relative px-2 cursor-pointer hover:bg-black-400" role="option" @click="clickedSublistOption($encode('No Series'))">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="font-normal block truncate py-2 text-xs text-white text-opacity-80">No Series</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<template v-for="item in sublistItems">
|
||||||
|
<li :key="item.value" class="text-gray-50 select-none relative px-2 cursor-pointer hover:bg-black-400" :class="`${sublist}.${item.value}` === selected ? 'bg-primary bg-opacity-50' : ''" role="option" @click="clickedSublistOption(item.value)">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="font-normal truncate py-2 text-xs">{{ item.text }}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: String,
|
||||||
|
isSeries: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showMenu: false,
|
||||||
|
sublist: null,
|
||||||
|
seriesItems: [
|
||||||
|
{
|
||||||
|
text: 'All',
|
||||||
|
value: 'all'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Genre',
|
||||||
|
value: 'genres',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Tag',
|
||||||
|
value: 'tags',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Authors',
|
||||||
|
value: 'authors',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Narrator',
|
||||||
|
value: 'narrators',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Language',
|
||||||
|
value: 'languages',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Series Progress',
|
||||||
|
value: 'progress',
|
||||||
|
sublist: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
bookItems: [
|
||||||
|
{
|
||||||
|
text: 'All',
|
||||||
|
value: 'all'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Genre',
|
||||||
|
value: 'genres',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Tag',
|
||||||
|
value: 'tags',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Series',
|
||||||
|
value: 'series',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Authors',
|
||||||
|
value: 'authors',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Narrator',
|
||||||
|
value: 'narrators',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Language',
|
||||||
|
value: 'languages',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Progress',
|
||||||
|
value: 'progress',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Missing',
|
||||||
|
value: 'missing',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Issues',
|
||||||
|
value: 'issues',
|
||||||
|
sublist: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'RSS Feed Open',
|
||||||
|
value: 'feed-open',
|
||||||
|
sublist: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
podcastItems: [
|
||||||
|
{
|
||||||
|
text: 'All',
|
||||||
|
value: 'all'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Genre',
|
||||||
|
value: 'genres',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Tag',
|
||||||
|
value: 'tags',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Issues',
|
||||||
|
value: 'issues',
|
||||||
|
sublist: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
showMenu(newVal) {
|
||||||
|
if (!newVal) {
|
||||||
|
if (this.sublist && !this.selectedItemSublist) this.sublist = null
|
||||||
|
if (!this.sublist && this.selectedItemSublist) this.sublist = this.selectedItemSublist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
selected: {
|
||||||
|
get() {
|
||||||
|
return this.value
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isPodcast() {
|
||||||
|
return this.$store.getters['libraries/getCurrentLibraryMediaType'] == 'podcast'
|
||||||
|
},
|
||||||
|
selectItems() {
|
||||||
|
if (this.isSeries) return this.seriesItems
|
||||||
|
if (this.isPodcast) return this.podcastItems
|
||||||
|
return this.bookItems
|
||||||
|
},
|
||||||
|
selectedItemSublist() {
|
||||||
|
return this.selected && this.selected.includes('.') ? this.selected.split('.')[0] : false
|
||||||
|
},
|
||||||
|
selectedText() {
|
||||||
|
if (!this.selected) return ''
|
||||||
|
var parts = this.selected.split('.')
|
||||||
|
var filterName = this.selectItems.find((i) => i.value === parts[0])
|
||||||
|
var filterValue = null
|
||||||
|
if (parts.length > 1) {
|
||||||
|
var decoded = this.$decode(parts[1])
|
||||||
|
if (decoded.startsWith('aut_')) {
|
||||||
|
var author = this.authors.find((au) => au.id == decoded)
|
||||||
|
if (author) filterValue = author.name
|
||||||
|
} else if (decoded.startsWith('ser_')) {
|
||||||
|
var series = this.series.find((se) => se.id == decoded)
|
||||||
|
if (series) filterValue = series.name
|
||||||
|
} else {
|
||||||
|
filterValue = decoded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filterName && filterValue) {
|
||||||
|
return `${filterName.text}: ${filterValue}`
|
||||||
|
} else if (filterName) {
|
||||||
|
return filterName.text
|
||||||
|
} else if (filterValue) {
|
||||||
|
return filterValue
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
genres() {
|
||||||
|
return this.filterData.genres || []
|
||||||
|
},
|
||||||
|
tags() {
|
||||||
|
return this.filterData.tags || []
|
||||||
|
},
|
||||||
|
series() {
|
||||||
|
return this.filterData.series || []
|
||||||
|
},
|
||||||
|
authors() {
|
||||||
|
return this.filterData.authors || []
|
||||||
|
},
|
||||||
|
narrators() {
|
||||||
|
return this.filterData.narrators || []
|
||||||
|
},
|
||||||
|
languages() {
|
||||||
|
return this.filterData.languages || []
|
||||||
|
},
|
||||||
|
progress() {
|
||||||
|
return ['Finished', 'In Progress', 'Not Started', 'Not Finished']
|
||||||
|
},
|
||||||
|
missing() {
|
||||||
|
return ['ASIN', 'ISBN', 'Subtitle', 'Author', 'Publish Year', 'Series', 'Description', 'Genres', 'Tags', 'Narrator', 'Publisher', 'Language']
|
||||||
|
},
|
||||||
|
sublistItems() {
|
||||||
|
return (this[this.sublist] || []).map((item) => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
return {
|
||||||
|
text: item,
|
||||||
|
value: this.$encode(item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
text: item.name,
|
||||||
|
value: this.$encode(item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
filterData() {
|
||||||
|
return this.$store.state.libraries.filterData || {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clearSelected() {
|
||||||
|
this.selected = 'all'
|
||||||
|
this.showMenu = false
|
||||||
|
this.$nextTick(() => this.$emit('change', 'all'))
|
||||||
|
},
|
||||||
|
clickOutside() {
|
||||||
|
if (!this.selectedItemSublist) this.sublist = null
|
||||||
|
this.showMenu = false
|
||||||
|
},
|
||||||
|
clickedSublistOption(item) {
|
||||||
|
this.clickedOption({ value: `${this.sublist}.${item}` })
|
||||||
|
},
|
||||||
|
clickedOption(option) {
|
||||||
|
if (option.sublist) {
|
||||||
|
this.sublist = option.value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var val = option.value
|
||||||
|
if (this.selected === val) {
|
||||||
|
this.showMenu = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.selected = val
|
||||||
|
this.showMenu = false
|
||||||
|
this.$nextTick(() => this.$emit('change', val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -11,8 +11,8 @@
|
|||||||
<ui-btn :disabled="processing" small class="ml-2 h-9" @click="clearSelected">Cancel</ui-btn>
|
<ui-btn :disabled="processing" small class="ml-2 h-9" @click="clearSelected">Cancel</ui-btn>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<controls-episode-filter-select v-model="filterKey" class="w-36 md:w-36 h-9 ml-1 sm:ml-4" />
|
<controls-filter-select v-model="filterKey" :items="filterItems" class="w-32 md:w-36 h-9 ml-1 sm:ml-4" />
|
||||||
<controls-sort-select v-model="sortKey" :descending.sync="sortDesc" :items="sortItems" class="w-36 sm:w-44 md:w-48 h-9 ml-1 sm:ml-4" />
|
<controls-sort-select v-model="sortKey" :descending.sync="sortDesc" :items="sortItems" class="w-32 sm:w-44 md:w-48 h-9 ml-1 sm:ml-4" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!episodes.length" class="py-4 text-center text-lg">No Episodes</p>
|
<p v-if="!episodes.length" class="py-4 text-center text-lg">No Episodes</p>
|
||||||
@ -60,6 +60,24 @@ export default {
|
|||||||
text: 'Episode',
|
text: 'Episode',
|
||||||
value: 'episode'
|
value: 'episode'
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
filterItems: [
|
||||||
|
{
|
||||||
|
value: 'all',
|
||||||
|
text: 'Show All'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'incomplete',
|
||||||
|
text: 'Incomplete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'complete',
|
||||||
|
text: 'Complete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'in_progress',
|
||||||
|
text: 'In Progress'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -4,6 +4,6 @@ export default function ({ store, redirect, route, app }) {
|
|||||||
if (route.name === 'batch' || route.name === 'index') {
|
if (route.name === 'batch' || route.name === 'index') {
|
||||||
return redirect('/login')
|
return redirect('/login')
|
||||||
}
|
}
|
||||||
return redirect(`/login?redirect=${route.fullPath}`)
|
return redirect(`/login?redirect=${encodeURIComponent(route.fullPath)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,12 +15,17 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set series sort by
|
// Set series sort by
|
||||||
if (query.sort && params.id === 'series') {
|
if (params.id === 'series') {
|
||||||
|
console.log('Series page', query)
|
||||||
|
if (query.sort) {
|
||||||
store.commit('libraries/setSeriesSortBy', query.sort)
|
store.commit('libraries/setSeriesSortBy', query.sort)
|
||||||
store.commit('libraries/setSeriesSortDesc', !!query.desc)
|
store.commit('libraries/setSeriesSortDesc', !!query.desc)
|
||||||
}
|
}
|
||||||
// Set filter by
|
if (query.filter) {
|
||||||
if (query.filter && params.id !== 'series') {
|
console.log('has filter', query.filter)
|
||||||
|
store.commit('libraries/setSeriesFilterBy', query.filter)
|
||||||
|
}
|
||||||
|
} else if (query.filter) {
|
||||||
store.dispatch('user/updateUserSettings', { filterBy: query.filter })
|
store.dispatch('user/updateUserSettings', { filterBy: query.filter })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,8 @@ export const state = () => ({
|
|||||||
folderLastUpdate: 0,
|
folderLastUpdate: 0,
|
||||||
filterData: null,
|
filterData: null,
|
||||||
seriesSortBy: 'name',
|
seriesSortBy: 'name',
|
||||||
seriesSortDesc: false
|
seriesSortDesc: false,
|
||||||
|
seriesFilterBy: 'all'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
@ -297,5 +298,8 @@ export const mutations = {
|
|||||||
},
|
},
|
||||||
setSeriesSortDesc(state, sortDesc) {
|
setSeriesSortDesc(state, sortDesc) {
|
||||||
state.seriesSortDesc = sortDesc
|
state.seriesSortDesc = sortDesc
|
||||||
|
},
|
||||||
|
setSeriesFilterBy(state, filterBy) {
|
||||||
|
state.seriesFilterBy = filterBy
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -275,7 +275,8 @@ class LibraryController {
|
|||||||
minified: req.query.minified === '1'
|
minified: req.query.minified === '1'
|
||||||
}
|
}
|
||||||
|
|
||||||
var series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, payload.minified)
|
var series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, payload.filterBy, req.user, payload.minified)
|
||||||
|
|
||||||
const direction = payload.sortDesc ? 'desc' : 'asc'
|
const direction = payload.sortDesc ? 'desc' : 'asc'
|
||||||
series = naturalSort(series).by([
|
series = naturalSort(series).by([
|
||||||
{
|
{
|
||||||
|
@ -67,6 +67,45 @@ module.exports = {
|
|||||||
return filtered
|
return filtered
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Returns false if should be filtered out
|
||||||
|
checkFilterForSeriesLibraryItem(libraryItem, filterBy) {
|
||||||
|
var searchGroups = ['genres', 'tags', 'authors', 'progress', 'narrators', 'languages']
|
||||||
|
var group = searchGroups.find(_group => filterBy.startsWith(_group + '.'))
|
||||||
|
if (group) {
|
||||||
|
var filterVal = filterBy.replace(`${group}.`, '')
|
||||||
|
var filter = this.decode(filterVal)
|
||||||
|
|
||||||
|
if (group === 'genres') return libraryItem.media.metadata && libraryItem.media.metadata.genres.includes(filter)
|
||||||
|
else if (group === 'tags') return libraryItem.media.tags.includes(filter)
|
||||||
|
else if (group === 'authors') return libraryItem.mediaType === 'book' && libraryItem.media.metadata.hasAuthor(filter)
|
||||||
|
else if (group === 'narrators') return libraryItem.mediaType === 'book' && libraryItem.media.metadata.hasNarrator(filter)
|
||||||
|
else if (group === 'languages') {
|
||||||
|
return libraryItem.media.metadata && libraryItem.media.metadata.language === filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Return false to filter out series
|
||||||
|
checkSeriesProgressFilter(series, filterBy, user) {
|
||||||
|
const filter = this.decode(filterBy.split('.')[1])
|
||||||
|
|
||||||
|
var numBooksStartedOrFinished = 0
|
||||||
|
for (const libraryItem of series.books) {
|
||||||
|
const itemProgress = user.getMediaProgress(libraryItem.id)
|
||||||
|
if (filter === 'Finished' && (!itemProgress || !itemProgress.isFinished)) return false
|
||||||
|
if (filter === 'Not Started' && itemProgress) return false
|
||||||
|
if (itemProgress) numBooksStartedOrFinished++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numBooksStartedOrFinished === series.books.length) { // Completely finished series
|
||||||
|
if (filter === 'Not Finished') return false
|
||||||
|
} else if (numBooksStartedOrFinished === 0 && filter === 'In Progress') { // Series not started
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
getDistinctFilterDataNew(libraryItems) {
|
getDistinctFilterDataNew(libraryItems) {
|
||||||
var data = {
|
var data = {
|
||||||
authors: [],
|
authors: [],
|
||||||
@ -114,10 +153,27 @@ module.exports = {
|
|||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
|
|
||||||
getSeriesFromBooks(books, allSeries, minified = false) {
|
getSeriesFromBooks(books, allSeries, filterBy, user, minified = false) {
|
||||||
const _series = {}
|
const _series = {}
|
||||||
|
const seriesToFilterOut = {}
|
||||||
books.forEach((libraryItem) => {
|
books.forEach((libraryItem) => {
|
||||||
const bookSeries = libraryItem.media.metadata.series || []
|
// get all book series for item that is not already filtered out
|
||||||
|
const bookSeries = (libraryItem.media.metadata.series || []).filter(se => !seriesToFilterOut[se.id])
|
||||||
|
if (!bookSeries.length) return
|
||||||
|
|
||||||
|
if (filterBy && user && !filterBy.startsWith('progress.')) { // Series progress filters are evaluated after grouping
|
||||||
|
// If a single book in a series is filtered out then filter out the entire series
|
||||||
|
if (!this.checkFilterForSeriesLibraryItem(libraryItem, filterBy)) {
|
||||||
|
// filter out this library item
|
||||||
|
bookSeries.forEach((bookSeriesObj) => {
|
||||||
|
// flag series to filter it out
|
||||||
|
seriesToFilterOut[bookSeriesObj.id] = true
|
||||||
|
delete _series[bookSeriesObj.id]
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bookSeries.forEach((bookSeriesObj) => {
|
bookSeries.forEach((bookSeriesObj) => {
|
||||||
const series = allSeries.find(se => se.id === bookSeriesObj.id)
|
const series = allSeries.find(se => se.id === bookSeriesObj.id)
|
||||||
|
|
||||||
@ -140,7 +196,15 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return Object.values(_series).map((series) => {
|
|
||||||
|
var seriesItems = Object.values(_series)
|
||||||
|
|
||||||
|
// check progress filter
|
||||||
|
if (filterBy && filterBy.startsWith('progress.') && user) {
|
||||||
|
seriesItems = seriesItems.filter(se => this.checkSeriesProgressFilter(se, filterBy, user))
|
||||||
|
}
|
||||||
|
|
||||||
|
return seriesItems.map((series) => {
|
||||||
series.books = naturalSort(series.books).asc(li => li.sequence)
|
series.books = naturalSort(series.books).asc(li => li.sequence)
|
||||||
return series
|
return series
|
||||||
})
|
})
|
||||||
@ -216,7 +280,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
collapseBookSeries(libraryItems, series) {
|
collapseBookSeries(libraryItems, series) {
|
||||||
var seriesObjects = this.getSeriesFromBooks(libraryItems, series, true)
|
var seriesObjects = this.getSeriesFromBooks(libraryItems, series, null, null, true)
|
||||||
var seriesToUse = {}
|
var seriesToUse = {}
|
||||||
var libraryItemIdsToHide = []
|
var libraryItemIdsToHide = []
|
||||||
seriesObjects.forEach((series) => {
|
seriesObjects.forEach((series) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user